From 27ef798792778d8b293e678e68e58f2dcf69c387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danyi=20D=C3=A1vid?= Date: Sun, 10 Jan 2021 19:19:37 +0100 Subject: [PATCH] * Initial version --- .gitignore | 2 + .phan/config.php | 365 ++ README.md | 27 + composer.json | 40 + composer.lock | 3818 +++++++++++++++++ src/ConfigProvider.php | 68 + src/Form/Element/ElementFactory.php | 18 + .../InvalidRepositoryResultException.php | 9 + src/Form/Element/ObjectMultiCheckbox.php | 81 + src/Form/Element/ObjectRadio.php | 72 + src/Form/Element/ObjectSelect.php | 87 + src/Form/Element/Proxy.php | 657 +++ src/Hydrator/DoctrineObject.php | 621 +++ src/Hydrator/DoctrineObjectFactory.php | 15 + src/Hydrator/Filter/PropertyName.php | 66 + .../Strategy/AbstractCollectionStrategy.php | 190 + .../Strategy/AllowRemoveByReference.php | 61 + src/Hydrator/Strategy/AllowRemoveByValue.php | 77 + .../Strategy/DisallowRemoveByReference.php | 54 + .../Strategy/DisallowRemoveByValue.php | 72 + src/Validator/NoObjectExists.php | 43 + src/Validator/ObjectExists.php | 177 + .../Service/AbstractValidatorFactory.php | 112 + .../Exception/ServiceCreationException.php | 16 + .../Service/NoObjectExistsFactory.php | 30 + src/Validator/Service/ObjectExistsFactory.php | 30 + src/Validator/Service/UniqueObjectFactory.php | 24 + src/Validator/UniqueObject.php | 166 + 28 files changed, 6998 insertions(+) create mode 100644 .gitignore create mode 100644 .phan/config.php create mode 100644 README.md create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 src/ConfigProvider.php create mode 100644 src/Form/Element/ElementFactory.php create mode 100644 src/Form/Element/Exception/InvalidRepositoryResultException.php create mode 100644 src/Form/Element/ObjectMultiCheckbox.php create mode 100644 src/Form/Element/ObjectRadio.php create mode 100644 src/Form/Element/ObjectSelect.php create mode 100644 src/Form/Element/Proxy.php create mode 100755 src/Hydrator/DoctrineObject.php create mode 100644 src/Hydrator/DoctrineObjectFactory.php create mode 100644 src/Hydrator/Filter/PropertyName.php create mode 100644 src/Hydrator/Strategy/AbstractCollectionStrategy.php create mode 100644 src/Hydrator/Strategy/AllowRemoveByReference.php create mode 100644 src/Hydrator/Strategy/AllowRemoveByValue.php create mode 100644 src/Hydrator/Strategy/DisallowRemoveByReference.php create mode 100644 src/Hydrator/Strategy/DisallowRemoveByValue.php create mode 100644 src/Validator/NoObjectExists.php create mode 100644 src/Validator/ObjectExists.php create mode 100644 src/Validator/Service/AbstractValidatorFactory.php create mode 100644 src/Validator/Service/Exception/ServiceCreationException.php create mode 100644 src/Validator/Service/NoObjectExistsFactory.php create mode 100644 src/Validator/Service/ObjectExistsFactory.php create mode 100644 src/Validator/Service/UniqueObjectFactory.php create mode 100644 src/Validator/UniqueObject.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8673c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.idea +/vendor/ diff --git a/.phan/config.php b/.phan/config.php new file mode 100644 index 0000000..9aaca18 --- /dev/null +++ b/.phan/config.php @@ -0,0 +1,365 @@ + '7.4', + + // If enabled, missing properties will be created when + // they are first seen. If false, we'll report an + // error message if there is an attempt to write + // to a class property that wasn't explicitly + // defined. + 'allow_missing_properties' => false, + + // If enabled, null can be cast to any type and any + // type can be cast to null. Setting this to true + // will cut down on false positives. + 'null_casts_as_any_type' => false, + + // If enabled, allow null to be cast as any array-like type. + // + // This is an incremental step in migrating away from `null_casts_as_any_type`. + // If `null_casts_as_any_type` is true, this has no effect. + 'null_casts_as_array' => true, + + // If enabled, allow any array-like type to be cast to null. + // This is an incremental step in migrating away from `null_casts_as_any_type`. + // If `null_casts_as_any_type` is true, this has no effect. + 'array_casts_as_null' => true, + + // If enabled, scalars (int, float, bool, string, null) + // are treated as if they can cast to each other. + // This does not affect checks of array keys. See `scalar_array_key_cast`. + 'scalar_implicit_cast' => false, + + // If enabled, any scalar array keys (int, string) + // are treated as if they can cast to each other. + // E.g. `array` can cast to `array` and vice versa. + // Normally, a scalar type such as int could only cast to/from int and mixed. + 'scalar_array_key_cast' => true, + + // If this has entries, scalars (int, float, bool, string, null) + // are allowed to perform the casts listed. + // + // E.g. `['int' => ['float', 'string'], 'float' => ['int'], 'string' => ['int'], 'null' => ['string']]` + // allows casting null to a string, but not vice versa. + // (subset of `scalar_implicit_cast`) + 'scalar_implicit_partial' => [], + + // If enabled, Phan will warn if **any** type in a method invocation's object + // is definitely not an object, + // or if **any** type in an invoked expression is not a callable. + // Setting this to true will introduce numerous false positives + // (and reveal some bugs). + 'strict_method_checking' => false, + + // If enabled, Phan will warn if **any** type of the object expression for a property access + // does not contain that property. + 'strict_object_checking' => false, + + // If enabled, Phan will warn if **any** type in the argument's union type + // cannot be cast to a type in the parameter's expected union type. + // Setting this to true will introduce numerous false positives + // (and reveal some bugs). + 'strict_param_checking' => false, + + // If enabled, Phan will warn if **any** type in a property assignment's union type + // cannot be cast to a type in the property's declared union type. + // Setting this to true will introduce numerous false positives + // (and reveal some bugs). + 'strict_property_checking' => false, + + // If enabled, Phan will warn if **any** type in a returned value's union type + // cannot be cast to the declared return type. + // Setting this to true will introduce numerous false positives + // (and reveal some bugs). + 'strict_return_checking' => false, + + // If true, seemingly undeclared variables in the global + // scope will be ignored. + // + // This is useful for projects with complicated cross-file + // globals that you have no hope of fixing. + 'ignore_undeclared_variables_in_global_scope' => true, + + // Set this to false to emit `PhanUndeclaredFunction` issues for internal functions that Phan has signatures for, + // but aren't available in the codebase, or from Reflection. + // (may lead to false positives if an extension isn't loaded) + // + // If this is true(default), then Phan will not warn. + // + // Even when this is false, Phan will still infer return values and check parameters of internal functions + // if Phan has the signatures. + 'ignore_undeclared_functions_with_known_signatures' => true, + + // Backwards Compatibility Checking. This is slow + // and expensive, but you should consider running + // it before upgrading your version of PHP to a + // new version that has backward compatibility + // breaks. + // + // If you are migrating from PHP 5 to PHP 7, + // you should also look into using + // [php7cc (no longer maintained)](https://github.com/sstalle/php7cc) + // and [php7mar](https://github.com/Alexia/php7mar), + // which have different backwards compatibility checks. + // + // If you are still using versions of php older than 5.6, + // `PHP53CompatibilityPlugin` may be worth looking into if you are not running + // syntax checks for php 5.3 through another method such as + // `InvokePHPNativeSyntaxCheckPlugin` (see .phan/plugins/README.md). + 'backward_compatibility_checks' => false, + + // If true, check to make sure the return type declared + // in the doc-block (if any) matches the return type + // declared in the method signature. + 'check_docblock_signature_return_type_match' => false, + + // This setting maps case-insensitive strings to union types. + // + // This is useful if a project uses phpdoc that differs from the phpdoc2 standard. + // + // If the corresponding value is the empty string, + // then Phan will ignore that union type (E.g. can ignore 'the' in `@return the value`) + // + // If the corresponding value is not empty, + // then Phan will act as though it saw the corresponding UnionTypes(s) + // when the keys show up in a UnionType of `@param`, `@return`, `@var`, `@property`, etc. + // + // This matches the **entire string**, not parts of the string. + // (E.g. `@return the|null` will still look for a class with the name `the`, but `@return the` will be ignored with the below setting) + // + // (These are not aliases, this setting is ignored outside of doc comments). + // (Phan does not check if classes with these names exist) + // + // Example setting: `['unknown' => '', 'number' => 'int|float', 'char' => 'string', 'long' => 'int', 'the' => '']` + 'phpdoc_type_mapping' => [], + + // Set to true in order to attempt to detect dead + // (unreferenced) code. Keep in mind that the + // results will only be a guess given that classes, + // properties, constants and methods can be referenced + // as variables (like `$class->$property` or + // `$class->$method()`) in ways that we're unable + // to make sense of. + // + // To more aggressively detect dead code, + // you may want to set `dead_code_detection_prefer_false_negative` to `false`. + 'dead_code_detection' => false, + + // Set to true in order to attempt to detect unused variables. + // `dead_code_detection` will also enable unused variable detection. + // + // This has a few known false positives, e.g. for loops or branches. + 'unused_variable_detection' => false, + + // Set to true in order to attempt to detect redundant and impossible conditions. + // + // This has some false positives involving loops, + // variables set in branches of loops, and global variables. + 'redundant_condition_detection' => false, + + // If enabled, Phan will act as though it's certain of real return types of a subset of internal functions, + // even if those return types aren't available in reflection (real types were taken from php 7.3 or 8.0-dev, depending on target_php_version). + // + // Note that with php 7 and earlier, php would return null or false for many internal functions if the argument types or counts were incorrect. + // As a result, enabling this setting with target_php_version 8.0 may result in false positives for `--redundant-condition-detection` when codebases also support php 7.x. + 'assume_real_types_for_internal_functions' => false, + + // If true, this runs a quick version of checks that takes less + // time at the cost of not running as thorough + // of an analysis. You should consider setting this + // to true only when you wish you had more **undiagnosed** issues + // to fix in your code base. + // + // In quick-mode the scanner doesn't rescan a function + // or a method's code block every time a call is seen. + // This means that the problem here won't be detected: + // + // ```php + // false, + + // Override to hardcode existence and types of (non-builtin) globals in the global scope. + // Class names should be prefixed with `\`. + // + // (E.g. `['_FOO' => '\FooClass', 'page' => '\PageClass', 'userId' => 'int']`) + 'globals_type_map' => [], + + // The minimum severity level to report on. This can be + // set to `Issue::SEVERITY_LOW`, `Issue::SEVERITY_NORMAL` or + // `Issue::SEVERITY_CRITICAL`. Setting it to only + // critical issues is a good place to start on a big + // sloppy mature code base. + 'minimum_severity' => Issue::SEVERITY_LOW, + + // Add any issue types (such as `'PhanUndeclaredMethod'`) + // to this list to inhibit them from being reported. + 'suppress_issue_types' => [], + + // A regular expression to match files to be excluded + // from parsing and analysis and will not be read at all. + // + // This is useful for excluding groups of test or example + // directories/files, unanalyzable files, or files that + // can't be removed for whatever reason. + // (e.g. `'@Test\.php$@'`, or `'@vendor/.*/(tests|Tests)/@'`) + 'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@', + + // A list of files that will be excluded from parsing and analysis + // and will not be read at all. + // + // This is useful for excluding hopelessly unanalyzable + // files that can't be removed for whatever reason. + 'exclude_file_list' => [], + + // A directory list that defines files that will be excluded + // from static analysis, but whose class and method + // information should be included. + // + // Generally, you'll want to include the directories for + // third-party code (such as "vendor/") in this list. + // + // n.b.: If you'd like to parse but not analyze 3rd + // party code, directories containing that code + // should be added to the `directory_list` as well as + // to `exclude_analysis_directory_list`. + 'exclude_analysis_directory_list' => [ + 'vendor/', + ], + + // Enable this to enable checks of require/include statements referring to valid paths. + // The settings `include_paths` and `warn_about_relative_include_statement` affect the checks. + 'enable_include_path_checks' => true, + + // The number of processes to fork off during the analysis + // phase. + 'processes' => 1, + + // List of case-insensitive file extensions supported by Phan. + // (e.g. `['php', 'html', 'htm']`) + 'analyzed_file_extensions' => [ + 'php', + ], + + // You can put paths to stubs of internal extensions in this config option. + // If the corresponding extension is **not** loaded, then Phan will use the stubs instead. + // Phan will continue using its detailed type annotations, + // but load the constants, classes, functions, and classes (and their Reflection types) + // from these stub files (doubling as valid php files). + // Use a different extension from php to avoid accidentally loading these. + // The `tools/make_stubs` script can be used to generate your own stubs (compatible with php 7.0+ right now) + // + // (e.g. `['xdebug' => '.phan/internal_stubs/xdebug.phan_php']`) + 'autoload_internal_extension_signatures' => [], + + // A list of plugin files to execute. + // + // Plugins which are bundled with Phan can be added here by providing their name (e.g. `'AlwaysReturnPlugin'`) + // + // Documentation about available bundled plugins can be found [here](https://github.com/phan/phan/tree/v4/.phan/plugins). + // + // Alternately, you can pass in the full path to a PHP file with the plugin's implementation (e.g. `'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php'`) + 'plugins' => [ + 'AlwaysReturnPlugin', + 'PregRegexCheckerPlugin', + 'UnreachableCodePlugin', + ], + + // A list of directories that should be parsed for class and + // method information. After excluding the directories + // defined in `exclude_analysis_directory_list`, the remaining + // files will be statically analyzed for errors. + // + // Thus, both first-party and third-party code being used by + // your application should be included in this list. + 'directory_list' => [ + 'src', + 'vendor/container-interop/container-interop/src', + 'vendor/laminas/laminas-eventmanager/src', + 'vendor/laminas/laminas-filter/src', + 'vendor/laminas/laminas-form/src', + 'vendor/laminas/laminas-hydrator/src', + 'vendor/laminas/laminas-inputfilter/src', + 'vendor/laminas/laminas-servicemanager/src', + 'vendor/laminas/laminas-stdlib/src', + 'vendor/laminas/laminas-validator/src', + 'vendor/phan/phan/src/Phan', + 'vendor/psr/container/src', + 'vendor/roave/psr-container-doctrine/src', + 'vendor/doctrine/common/lib', + 'vendor/doctrine/collections/lib', + 'vendor/doctrine/inflector/lib', + 'vendor/doctrine/orm/lib', + 'vendor/doctrine/persistence/lib', + ], + + // A list of individual files to include in analysis + // with a path relative to the root directory of the + // project. + 'file_list' => [], +]; diff --git a/README.md b/README.md new file mode 100644 index 0000000..844ea34 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# DoctrineMezzioModule + +Simple integration of doctrine into mezzio applications. + +## Getting Started + +You'll have to add the VCS to your global composer config.json: +```bash +$ cat ~/.composer/config.json +``` +```json +{ + "config": {}, + "repositories": [ + { + "type": "vcs", + "url": "https://gogs.ragnarok.yvan.hu/yvan/mezzio-api-skeleton.git" + } + ] +} +``` + +Install the module in your existing mezzio application with composer: + +```bash +$ composer require yvan/mezzio-api-skeleton --stability=dev +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..552da77 --- /dev/null +++ b/composer.json @@ -0,0 +1,40 @@ +{ + "name": "yvan/doctrine-mezzio-module", + "description": "Doctrine module for mezzio middleware applications", + "type": "library", + "config": { + "sort-packages": true + }, + "require": { + "php": "^7.4 || ^8.0", + "laminas/laminas-eventmanager": "^3.3", + "laminas/laminas-servicemanager": "^3.5", + "laminas/laminas-validator": "^2.13", + "laminas/laminas-form": "^2.15", + "roave/psr-container-doctrine": "^2.2" + }, + "autoload": { + "psr-4": { + "DoctrineMezzioModule\\": "src/" + } + }, + "authors": [ + { + "name": "Danyi Dávid", + "email": "danyi.david@gmail.com" + } + ], + "require-dev": { + "phan/phan": "^4.0" + }, + "scripts": { + "phan": "phan", + "phan-init": "phan --init" + }, + "extra": { + "laminas": { + "component": "DoctrineMezzioModule", + "config-provider": "DoctrineMezzioModule\\ConfigProvider" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..d6a2088 --- /dev/null +++ b/composer.lock @@ -0,0 +1,3818 @@ +{ + "_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#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "25e1f2c374a9f4e31b1efbaf7c8e5115", + "packages": [ + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.1", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-11-11T10:22:58+00:00" + }, + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "support": { + "issues": "https://github.com/container-interop/container-interop/issues", + "source": "https://github.com/container-interop/container-interop/tree/master" + }, + "abandoned": "psr/container", + "time": "2017-02-14T19:40:03+00:00" + }, + { + "name": "doctrine/annotations", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "ce77a7ba1770462cd705a91a151b6c3746f9c6ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/ce77a7ba1770462cd705a91a151b6c3746f9c6ad", + "reference": "ce77a7ba1770462cd705a91a151b6c3746f9c6ad", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/cache": "1.*", + "doctrine/coding-standard": "^6.0 || ^8.1", + "phpstan/phpstan": "^0.12.20", + "phpunit/phpunit": "^7.5 || ^9.1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.11.1" + }, + "time": "2020-10-26T10:28:16+00:00" + }, + { + "name": "doctrine/cache", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "13e3381b25847283a91948d04640543941309727" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/13e3381b25847283a91948d04640543941309727", + "reference": "13e3381b25847283a91948d04640543941309727", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "doctrine/coding-standard": "^6.0", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0", + "predis/predis": "~1.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/1.10.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2020-07-07T18:54:01+00:00" + }, + { + "name": "doctrine/collections", + "version": "1.6.7", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/55f8b799269a1a472457bd1a41b4f379d4cfba4a", + "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a", + "shasum": "" + }, + "require": { + "php": "^7.1.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan-shim": "^0.9.2", + "phpunit/phpunit": "^7.0", + "vimeo/psalm": "^3.8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/1.6.7" + }, + "time": "2020-07-27T17:53:49+00:00" + }, + { + "name": "doctrine/common", + "version": "2.13.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "f3812c026e557892c34ef37f6ab808a6b567da7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/f3812c026e557892c34ef37f6ab808a6b567da7f", + "reference": "f3812c026e557892c34ef37f6ab808a6b567da7f", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.0", + "doctrine/cache": "^1.0", + "doctrine/collections": "^1.0", + "doctrine/event-manager": "^1.0", + "doctrine/inflector": "^1.0", + "doctrine/lexer": "^1.0", + "doctrine/persistence": "^1.3.3", + "doctrine/reflection": "^1.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^1.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpunit/phpunit": "^7.0", + "squizlabs/php_codesniffer": "^3.0", + "symfony/phpunit-bridge": "^4.0.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, persistence interfaces, proxies, event system and much more.", + "homepage": "https://www.doctrine-project.org/projects/common.html", + "keywords": [ + "common", + "doctrine", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/common/issues", + "source": "https://github.com/doctrine/common/tree/2.13.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", + "type": "tidelift" + } + ], + "time": "2020-06-05T16:46:05+00:00" + }, + { + "name": "doctrine/dbal", + "version": "2.12.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "adce7a954a1c2f14f85e94aed90c8489af204086" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/adce7a954a1c2f14f85e94aed90c8489af204086", + "reference": "adce7a954a1c2f14f85e94aed90c8489af204086", + "shasum": "" + }, + "require": { + "doctrine/cache": "^1.0", + "doctrine/event-manager": "^1.0", + "ext-pdo": "*", + "php": "^7.3 || ^8" + }, + "require-dev": { + "doctrine/coding-standard": "^8.1", + "jetbrains/phpstorm-stubs": "^2019.1", + "phpstan/phpstan": "^0.12.40", + "phpunit/phpunit": "^9.4", + "psalm/plugin-phpunit": "^0.10.0", + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", + "vimeo/psalm": "^3.17.2" + }, + "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": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "lib/Doctrine/DBAL" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlanywhere", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/2.12.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2020-11-14T20:26:58+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2020-05-29T18:28:51+00:00" + }, + { + "name": "doctrine/inflector", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "4650c8b30c753a76bf44fb2ed00117d6f367490c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/4650c8b30c753a76bf44fb2ed00117d6f367490c", + "reference": "4650c8b30c753a76bf44fb2ed00117d6f367490c", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^7.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector", + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/1.4.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2020-05-29T07:19:59+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2020-05-25T17:44:05+00:00" + }, + { + "name": "doctrine/migrations", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/migrations.git", + "reference": "39520699043d9bfaaebeb81fa026bf2b02a8f735" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/39520699043d9bfaaebeb81fa026bf2b02a8f735", + "reference": "39520699043d9bfaaebeb81fa026bf2b02a8f735", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.8", + "doctrine/dbal": "^2.9", + "friendsofphp/proxy-manager-lts": "^1.0", + "php": "^7.1 || ^8.0", + "symfony/console": "^3.4||^4.4.16||^5.0", + "symfony/stopwatch": "^3.4||^4.0||^5.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.2", + "doctrine/orm": "^2.6", + "ext-pdo_sqlite": "*", + "jdorn/sql-formatter": "^1.1", + "mikey179/vfsstream": "^1.6", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/process": "^3.4||^4.0||^5.0", + "symfony/yaml": "^3.4||^4.0||^5.0" + }, + "suggest": { + "jdorn/sql-formatter": "Allows to generate formatted SQL with the diff command.", + "symfony/yaml": "Allows the use of yaml for migration configuration files." + }, + "bin": [ + "bin/doctrine-migrations" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Migrations\\": "lib/Doctrine/Migrations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Michael Simonson", + "email": "contact@mikesimonson.com" + } + ], + "description": "PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easily deploying changes to it. It is a very easy to use and a powerful tool.", + "homepage": "https://www.doctrine-project.org/projects/migrations.html", + "keywords": [ + "database", + "dbal", + "migrations", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/migrations/issues", + "source": "https://github.com/doctrine/migrations/tree/2.3.2" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fmigrations", + "type": "tidelift" + } + ], + "time": "2020-12-23T14:06:04+00:00" + }, + { + "name": "doctrine/orm", + "version": "2.7.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/orm.git", + "reference": "01187c9260cd085529ddd1273665217cae659640" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/orm/zipball/01187c9260cd085529ddd1273665217cae659640", + "reference": "01187c9260cd085529ddd1273665217cae659640", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.8", + "doctrine/annotations": "^1.11.1", + "doctrine/cache": "^1.9.1", + "doctrine/collections": "^1.5", + "doctrine/common": "^2.11 || ^3.0", + "doctrine/dbal": "^2.9.3", + "doctrine/event-manager": "^1.1", + "doctrine/inflector": "^1.0", + "doctrine/instantiator": "^1.3", + "doctrine/lexer": "^1.0", + "doctrine/persistence": "^1.3.3 || ^2.0", + "ext-pdo": "*", + "php": "^7.1", + "symfony/console": "^3.0|^4.0|^5.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.12.18", + "phpunit/phpunit": "^8.0", + "symfony/yaml": "^3.4|^4.0|^5.0", + "vimeo/psalm": "^3.11" + }, + "suggest": { + "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" + }, + "bin": [ + "bin/doctrine" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\ORM\\": "lib/Doctrine/ORM" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Object-Relational-Mapper for PHP", + "homepage": "https://www.doctrine-project.org/projects/orm.html", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/doctrine/orm/issues", + "source": "https://github.com/doctrine/orm/tree/2.7.5" + }, + "time": "2020-12-03T08:52:14+00:00" + }, + { + "name": "doctrine/persistence", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "7a6eac9fb6f61bba91328f15aa7547f4806ca288" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/7a6eac9fb6f61bba91328f15aa7547f4806ca288", + "reference": "7a6eac9fb6f61bba91328f15aa7547f4806ca288", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.0", + "doctrine/cache": "^1.0", + "doctrine/collections": "^1.0", + "doctrine/event-manager": "^1.0", + "doctrine/reflection": "^1.2", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.10@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^3.11" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common", + "Doctrine\\Persistence\\": "lib/Doctrine/Persistence" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/1.3.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fpersistence", + "type": "tidelift" + } + ], + "time": "2020-06-20T12:56:16+00:00" + }, + { + "name": "doctrine/reflection", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/reflection.git", + "reference": "fa587178be682efe90d005e3a322590d6ebb59a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/reflection/zipball/fa587178be682efe90d005e3a322590d6ebb59a5", + "reference": "fa587178be682efe90d005e3a322590d6ebb59a5", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.0", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0 || ^8.2.0", + "doctrine/common": "^2.10", + "phpstan/phpstan": "^0.11.0 || ^0.12.20", + "phpstan/phpstan-phpunit": "^0.11.0 || ^0.12.16", + "phpunit/phpunit": "^7.5 || ^9.1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Reflection project is a simple library used by the various Doctrine projects which adds some additional functionality on top of the reflection functionality that comes with PHP. It allows you to get the reflection information about classes, methods and properties statically.", + "homepage": "https://www.doctrine-project.org/projects/reflection.html", + "keywords": [ + "reflection", + "static" + ], + "support": { + "issues": "https://github.com/doctrine/reflection/issues", + "source": "https://github.com/doctrine/reflection/tree/1.2.2" + }, + "abandoned": "roave/better-reflection", + "time": "2020-10-27T21:46:55+00:00" + }, + { + "name": "friendsofphp/proxy-manager-lts", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/proxy-manager-lts.git", + "reference": "4a66e4e0d3279d3bb3722963b4294331fabe15bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/4a66e4e0d3279d3bb3722963b4294331fabe15bc", + "reference": "4a66e4e0d3279d3bb3722963b4294331fabe15bc", + "shasum": "" + }, + "require": { + "laminas/laminas-code": "~3.4.1|^4.0", + "php": ">=7.1", + "symfony/filesystem": "^4.4.17|^5.0" + }, + "conflict": { + "laminas/laminas-stdlib": "<3.2.1", + "zendframework/zend-stdlib": "<3.2.1" + }, + "replace": { + "ocramius/proxy-manager": "^2.1" + }, + "require-dev": { + "ext-phar": "*", + "symfony/phpunit-bridge": "^5.2" + }, + "type": "library", + "extra": { + "thanks": { + "name": "ocramius/proxy-manager", + "url": "https://github.com/Ocramius/ProxyManager" + } + }, + "autoload": { + "psr-4": { + "ProxyManager\\": "src/ProxyManager" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.io/" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + } + ], + "description": "Adding support for a wider range of PHP versions to ocramius/proxy-manager", + "homepage": "https://github.com/FriendsOfPHP/proxy-manager-lts", + "keywords": [ + "aop", + "lazy loading", + "proxy", + "proxy pattern", + "service proxies" + ], + "support": { + "issues": "https://github.com/FriendsOfPHP/proxy-manager-lts/issues", + "source": "https://github.com/FriendsOfPHP/proxy-manager-lts/tree/v1.0.2" + }, + "funding": [ + { + "url": "https://github.com/Ocramius", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ocramius/proxy-manager", + "type": "tidelift" + } + ], + "time": "2021-01-04T11:21:26+00:00" + }, + { + "name": "laminas/laminas-code", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-code.git", + "reference": "28a6d70ea8b8bca687d7163300e611ae33baf82a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/28a6d70ea8b8bca687d7163300e611ae33baf82a", + "reference": "28a6d70ea8b8bca687d7163300e611ae33baf82a", + "shasum": "" + }, + "require": { + "laminas/laminas-eventmanager": "^3.3", + "php": "^7.4 || ~8.0.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0" + }, + "replace": { + "zendframework/zend-code": "self.version" + }, + "require-dev": { + "doctrine/annotations": "^1.10.4", + "ext-phar": "*", + "laminas/laminas-coding-standard": "^2.1.4", + "laminas/laminas-stdlib": "^3.3.0", + "phpunit/phpunit": "^9.4.2", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3.1" + }, + "suggest": { + "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", + "laminas/laminas-stdlib": "Laminas\\Stdlib component", + "laminas/laminas-zendframework-bridge": "A bridge with Zend Framework" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Code\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", + "homepage": "https://laminas.dev", + "keywords": [ + "code", + "laminas", + "laminasframework" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-code/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-code/issues", + "rss": "https://github.com/laminas/laminas-code/releases.atom", + "source": "https://github.com/laminas/laminas-code" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2020-12-30T16:16:14+00:00" + }, + { + "name": "laminas/laminas-eventmanager", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-eventmanager.git", + "reference": "1940ccf30e058b2fd66f5a9d696f1b5e0027b082" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/1940ccf30e058b2fd66f5a9d696f1b5e0027b082", + "reference": "1940ccf30e058b2fd66f5a9d696f1b5e0027b082", + "shasum": "" + }, + "require": { + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.3 || ^8.0" + }, + "replace": { + "zendframework/zend-eventmanager": "^3.2.1" + }, + "require-dev": { + "container-interop/container-interop": "^1.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-stdlib": "^2.7.3 || ^3.0", + "phpbench/phpbench": "^0.17.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "container-interop/container-interop": "^1.1, to use the lazy listeners feature", + "laminas/laminas-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3.x-dev", + "dev-develop": "3.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laminas\\EventManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Trigger and listen to events within a PHP application", + "homepage": "https://laminas.dev", + "keywords": [ + "event", + "eventmanager", + "events", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-eventmanager/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-eventmanager/issues", + "rss": "https://github.com/laminas/laminas-eventmanager/releases.atom", + "source": "https://github.com/laminas/laminas-eventmanager" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2020-08-25T11:10:44+00:00" + }, + { + "name": "laminas/laminas-filter", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-filter.git", + "reference": "cfb40b104e92a0b52bee696b74f958798ad8faa4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/cfb40b104e92a0b52bee696b74f958798ad8faa4", + "reference": "cfb40b104e92a0b52bee696b74f958798ad8faa4", + "shasum": "" + }, + "require": { + "laminas/laminas-stdlib": "^3.3", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.3 || ~8.0.0" + }, + "conflict": { + "laminas/laminas-validator": "<2.10.1" + }, + "replace": { + "zendframework/zend-filter": "^2.9.2" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-crypt": "^3.2.1", + "laminas/laminas-servicemanager": "^3.3", + "laminas/laminas-uri": "^2.6", + "pear/archive_tar": "^1.4.3", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.3", + "psr/http-factory": "^1.0" + }, + "suggest": { + "laminas/laminas-crypt": "Laminas\\Crypt component, for encryption filters", + "laminas/laminas-i18n": "Laminas\\I18n component for filters depending on i18n functionality", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component, for using the filter chain functionality", + "laminas/laminas-uri": "Laminas\\Uri component, for the UriNormalize filter", + "psr/http-factory-implementation": "psr/http-factory-implementation, for creating file upload instances when consuming PSR-7 in file upload filters" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Filter", + "config-provider": "Laminas\\Filter\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Filter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Programmatically filter and normalize data and files", + "homepage": "https://laminas.dev", + "keywords": [ + "filter", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-filter/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-filter/issues", + "rss": "https://github.com/laminas/laminas-filter/releases.atom", + "source": "https://github.com/laminas/laminas-filter" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-01-01T14:37:45+00:00" + }, + { + "name": "laminas/laminas-form", + "version": "2.15.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-form.git", + "reference": "359cd372c565e18a17f32ccfeacdf21bba091ce2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-form/zipball/359cd372c565e18a17f32ccfeacdf21bba091ce2", + "reference": "359cd372c565e18a17f32ccfeacdf21bba091ce2", + "shasum": "" + }, + "require": { + "laminas/laminas-hydrator": "^1.1 || ^2.1 || ^3.0", + "laminas/laminas-inputfilter": "^2.8", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-form": "^2.14.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-code": "^2.6 || ^3.0", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-recaptcha": "^3.0.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-text": "^2.6", + "laminas/laminas-validator": "^2.6", + "laminas/laminas-view": "^2.6.2", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20" + }, + "suggest": { + "laminas/laminas-captcha": "^2.7.1, required for using CAPTCHA form elements", + "laminas/laminas-code": "^2.6 || ^3.0, required to use laminas-form annotations support", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0, reuired for laminas-form annotations support", + "laminas/laminas-i18n": "^2.6, required when using laminas-form view helpers", + "laminas/laminas-recaptcha": "in order to use the ReCaptcha form element", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3, required to use the form factories or provide services", + "laminas/laminas-view": "^2.6.2, required for using the laminas-form view helpers" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.15.x-dev", + "dev-develop": "2.16.x-dev" + }, + "laminas": { + "component": "Laminas\\Form", + "config-provider": "Laminas\\Form\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Form\\": "src/" + }, + "files": [ + "autoload/formElementManagerPolyfill.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Validate and display simple and complex forms, casting forms to business objects and vice versa", + "homepage": "https://laminas.dev", + "keywords": [ + "form", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-form/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-form/issues", + "rss": "https://github.com/laminas/laminas-form/releases.atom", + "source": "https://github.com/laminas/laminas-form" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2020-07-14T13:53:27+00:00" + }, + { + "name": "laminas/laminas-hydrator", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-hydrator.git", + "reference": "f0336699910478cc45c7e34ca0fc83bf118e48bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-hydrator/zipball/f0336699910478cc45c7e34ca0fc83bf118e48bc", + "reference": "f0336699910478cc45c7e34ca0fc83bf118e48bc", + "shasum": "" + }, + "require": { + "laminas/laminas-stdlib": "^3.3", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.3 || ~8.0.0" + }, + "replace": { + "zendframework/zend-hydrator": "^3.0.2" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-eventmanager": "^3.2.1", + "laminas/laminas-modulemanager": "^2.8", + "laminas/laminas-serializer": "^2.9", + "laminas/laminas-servicemanager": "^3.3.2", + "phpunit/phpunit": "~9.3.0", + "vimeo/psalm": "^3.16" + }, + "suggest": { + "laminas/laminas-eventmanager": "^3.2, to support aggregate hydrator usage", + "laminas/laminas-serializer": "^2.9, to use the SerializableStrategy", + "laminas/laminas-servicemanager": "^3.3, to support hydrator plugin manager usage" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Hydrator", + "config-provider": "Laminas\\Hydrator\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Hydrator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Serialize objects to arrays, and vice versa", + "homepage": "https://laminas.dev", + "keywords": [ + "hydrator", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-hydrator/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-hydrator/issues", + "rss": "https://github.com/laminas/laminas-hydrator/releases.atom", + "source": "https://github.com/laminas/laminas-hydrator" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2020-10-06T19:45:29+00:00" + }, + { + "name": "laminas/laminas-inputfilter", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-inputfilter.git", + "reference": "34ed067ae366a2171d6d174a04002e4eb17c166d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-inputfilter/zipball/34ed067ae366a2171d6d174a04002e4eb17c166d", + "reference": "34ed067ae366a2171d6d174a04002e4eb17c166d", + "shasum": "" + }, + "require": { + "laminas/laminas-filter": "^2.9.1", + "laminas/laminas-servicemanager": "^3.3.1", + "laminas/laminas-stdlib": "^3.0", + "laminas/laminas-validator": "^2.11", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.3 || ~8.0.0" + }, + "replace": { + "zendframework/zend-inputfilter": "^2.10.1" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.4.2", + "psr/http-message": "^1.0" + }, + "suggest": { + "psr/http-message-implementation": "PSR-7 is required if you wish to validate PSR-7 UploadedFileInterface payloads" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\InputFilter", + "config-provider": "Laminas\\InputFilter\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\InputFilter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Normalize and validate input sets from the web, APIs, the CLI, and more, including files", + "homepage": "https://laminas.dev", + "keywords": [ + "inputfilter", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-inputfilter/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-inputfilter/issues", + "rss": "https://github.com/laminas/laminas-inputfilter/releases.atom", + "source": "https://github.com/laminas/laminas-inputfilter" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2020-12-01T09:51:35+00:00" + }, + { + "name": "laminas/laminas-servicemanager", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-servicemanager.git", + "reference": "0c1ec25ff486d690c8aa8aeb93f560dab593aa78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/0c1ec25ff486d690c8aa8aeb93f560dab593aa78", + "reference": "0c1ec25ff486d690c8aa8aeb93f560dab593aa78", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.3 || ~8.0.0", + "psr/container": "^1.0" + }, + "conflict": { + "laminas/laminas-code": "<3.3.1", + "zendframework/zend-code": "<3.3.1" + }, + "provide": { + "container-interop/container-interop-implementation": "^1.2", + "psr/container-implementation": "^1.0" + }, + "replace": { + "zendframework/zend-servicemanager": "^3.4.0" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.0", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-container-config-test": "^0.3", + "laminas/laminas-dependency-plugin": "^2.1", + "mikey179/vfsstream": "^1.6.8", + "ocramius/proxy-manager": "^2.2.3", + "phpbench/phpbench": "^1.0.0-alpha3", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.4" + }, + "suggest": { + "ocramius/proxy-manager": "ProxyManager ^2.1.1 to handle lazy initialization of services" + }, + "bin": [ + "bin/generate-deps-for-config-factory", + "bin/generate-factory-for-class" + ], + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\ServiceManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Factory-Driven Dependency Injection Container", + "homepage": "https://laminas.dev", + "keywords": [ + "PSR-11", + "dependency-injection", + "di", + "dic", + "laminas", + "service-manager", + "servicemanager" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-servicemanager/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-servicemanager/issues", + "rss": "https://github.com/laminas/laminas-servicemanager/releases.atom", + "source": "https://github.com/laminas/laminas-servicemanager" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-01-10T15:50:33+00:00" + }, + { + "name": "laminas/laminas-stdlib", + "version": "3.3.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "d81c7ffe602ed0e6ecb18691019111c0f4bf1efe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/d81c7ffe602ed0e6ecb18691019111c0f4bf1efe", + "reference": "d81c7ffe602ed0e6ecb18691019111c0f4bf1efe", + "shasum": "" + }, + "require": { + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.3 || ^8.0" + }, + "replace": { + "zendframework/zend-stdlib": "^3.2.1" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpbench/phpbench": "^0.17.1", + "phpunit/phpunit": "~9.3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "SPL extensions, array utilities, error handlers, and more", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "stdlib" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-stdlib/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-stdlib/issues", + "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", + "source": "https://github.com/laminas/laminas-stdlib" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2020-11-19T20:18:59+00:00" + }, + { + "name": "laminas/laminas-validator", + "version": "2.14.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-validator.git", + "reference": "8da5e20ed7b2b8101c1de68ca8dc0180210ed23e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/8da5e20ed7b2b8101c1de68ca8dc0180210ed23e", + "reference": "8da5e20ed7b2b8101c1de68ca8dc0180210ed23e", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.1", + "laminas/laminas-stdlib": "^3.3", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.3 || ~8.0.0" + }, + "replace": { + "zendframework/zend-validator": "^2.13.0" + }, + "require-dev": { + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-http": "^2.14.2", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-math": "^2.6", + "laminas/laminas-servicemanager": "^2.7.11 || ^3.0.3", + "laminas/laminas-session": "^2.8", + "laminas/laminas-uri": "^2.7", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.3", + "psalm/plugin-phpunit": "^0.15.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "vimeo/psalm": "^4.3" + }, + "suggest": { + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-math": "Laminas\\Math component, required by the Csrf validator", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators", + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Validator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "validator" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-validator/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-validator/issues", + "rss": "https://github.com/laminas/laminas-validator/releases.atom", + "source": "https://github.com/laminas/laminas-validator" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-01-07T16:07:31+00:00" + }, + { + "name": "laminas/laminas-zendframework-bridge", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-zendframework-bridge.git", + "reference": "6ede70583e101030bcace4dcddd648f760ddf642" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/6ede70583e101030bcace4dcddd648f760ddf642", + "reference": "6ede70583e101030bcace4dcddd648f760ddf642", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1 || ^9.3", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laminas": { + "module": "Laminas\\ZendFrameworkBridge" + } + }, + "autoload": { + "files": [ + "src/autoload.php" + ], + "psr-4": { + "Laminas\\ZendFrameworkBridge\\": "src//" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Alias legacy ZF class names to Laminas Project equivalents.", + "keywords": [ + "ZendFramework", + "autoloading", + "laminas", + "zf" + ], + "support": { + "forum": "https://discourse.laminas.dev/", + "issues": "https://github.com/laminas/laminas-zendframework-bridge/issues", + "rss": "https://github.com/laminas/laminas-zendframework-bridge/releases.atom", + "source": "https://github.com/laminas/laminas-zendframework-bridge" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2020-09-14T14:23:00+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/master" + }, + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "roave/psr-container-doctrine", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/Roave/psr-container-doctrine.git", + "reference": "a0b2de4388603b6369e790dd8adfef0238e7d058" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/psr-container-doctrine/zipball/a0b2de4388603b6369e790dd8adfef0238e7d058", + "reference": "a0b2de4388603b6369e790dd8adfef0238e7d058", + "shasum": "" + }, + "require": { + "doctrine/common": "^2.6", + "doctrine/dbal": "^2.5", + "doctrine/migrations": "^2.2", + "doctrine/orm": "^2.5", + "php": ">=7.3.0,<7.5.0", + "psr/container": "^1.0.0" + }, + "require-dev": { + "doctrine/coding-standard": "^7.0", + "phpunit/phpunit": "^8.5", + "psalm/plugin-phpunit": "^0.9.0", + "vimeo/psalm": "^3.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Roave\\PsrContainerDoctrine\\": "src/" + }, + "files": [ + "bc-namespace-shim.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen", + "homepage": "https://github.com/dasprid" + }, + { + "name": "James Titcumb", + "email": "james@asgrim.com", + "homepage": "https://github.com/asgrim" + } + ], + "description": "Doctrine Factories for PSR-11 Containers", + "homepage": "https://github.com/Roave/psr-container-doctrine", + "support": { + "issues": "https://github.com/Roave/psr-container-doctrine/issues", + "source": "https://github.com/Roave/psr-container-doctrine/tree/2.2.0" + }, + "time": "2020-04-16T23:01:26+00:00" + }, + { + "name": "symfony/console", + "version": "v5.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "47c02526c532fb381374dab26df05e7313978976" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/47c02526c532fb381374dab26df05e7313978976", + "reference": "47c02526c532fb381374dab26df05e7313978976", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" + }, + "conflict": { + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "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", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-12-18T08:03:05+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "fa8f8cab6b65e2d99a118e082935344c5ba8c60d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/fa8f8cab6b65e2d99a118e082935344c5ba8c60d", + "reference": "fa8f8cab6b65e2d99a118e082935344c5ba8c60d", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "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", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-11-30T17:05:38+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "267a9adeb8ecb8071040a740930e077cdfb987af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/267a9adeb8ecb8071040a740930e077cdfb987af", + "reference": "267a9adeb8ecb8071040a740930e077cdfb987af", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "6e971c891537eb617a00bb07a43d182a6915faba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/6e971c891537eb617a00bb07a43d182a6915faba", + "reference": "6e971c891537eb617a00bb07a43d182a6915faba", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T17:09:11+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "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" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/master" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-07T11:33:47+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v5.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "2b105c0354f39a63038a1d8bf776ee92852813af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/2b105c0354f39a63038a1d8bf776ee92852813af", + "reference": "2b105c0354f39a63038a1d8bf776ee92852813af", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/service-contracts": "^1.0|^2" + }, + "type": "library", + "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", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v5.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-11-01T16:14:45+00:00" + }, + { + "name": "symfony/string", + "version": "v5.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", + "reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony String component", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-12-05T07:33:16+00:00" + } + ], + "packages-dev": [ + { + "name": "composer/semver", + "version": "3.2.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", + "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.54", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.2.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-11-13T08:59:24+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "f28d44c286812c714741478d968104c5e604a1d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/f28d44c286812c714741478d968104c5e604a1d4", + "reference": "f28d44c286812c714741478d968104c5e604a1d4", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/1.4.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-11-13T08:04:11+00:00" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.1.2", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "804ee564f1c7f0bc160d755fc5bdd17a04545070" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/804ee564f1c7f0bc160d755fc5bdd17a04545070", + "reference": "804ee564f1c7f0bc160d755fc5bdd17a04545070", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0", + "php": ">=7.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.1.2" + }, + "time": "2021-01-10T13:07:47+00:00" + }, + { + "name": "microsoft/tolerant-php-parser", + "version": "v0.0.23", + "source": { + "type": "git", + "url": "https://github.com/microsoft/tolerant-php-parser.git", + "reference": "1d76657e3271754515ace52501d3e427eca42ad0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/microsoft/tolerant-php-parser/zipball/1d76657e3271754515ace52501d3e427eca42ad0", + "reference": "1d76657e3271754515ace52501d3e427eca42ad0", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Microsoft\\PhpParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Lourens", + "email": "roblou@microsoft.com" + } + ], + "description": "Tolerant PHP-to-AST parser designed for IDE usage scenarios", + "support": { + "issues": "https://github.com/microsoft/tolerant-php-parser/issues", + "source": "https://github.com/microsoft/tolerant-php-parser/tree/v0.0.23" + }, + "time": "2020-09-13T17:29:12+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/e0f1e33a71587aca81be5cffbb9746510e1fe04e", + "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4 || ~7.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/master" + }, + "time": "2020-04-16T18:48:43+00:00" + }, + { + "name": "phan/phan", + "version": "4.0.2", + "source": { + "type": "git", + "url": "https://github.com/phan/phan.git", + "reference": "2e5f16a17aa909c91397c6fbba0309b4fef0df53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phan/phan/zipball/2e5f16a17aa909c91397c6fbba0309b4fef0df53", + "reference": "2e5f16a17aa909c91397c6fbba0309b4fef0df53", + "shasum": "" + }, + "require": { + "composer/semver": "^1.4|^2.0|^3.0", + "composer/xdebug-handler": "^1.3.2", + "ext-filter": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.0.4", + "microsoft/tolerant-php-parser": "0.0.23", + "netresearch/jsonmapper": "^1.6.0|^2.0|^3.0", + "php": "^7.2.0|^8.0.0", + "sabre/event": "^5.0.3", + "symfony/console": "^3.2|^4.0|^5.0", + "symfony/polyfill-mbstring": "^1.11.0", + "symfony/polyfill-php80": "^1.20.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.0" + }, + "suggest": { + "ext-ast": "Needed for parsing ASTs (unless --use-fallback-parser is used). 1.0.1+ is needed, 1.0.10+ is recommended.", + "ext-iconv": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8", + "ext-igbinary": "Improves performance of polyfill when ext-ast is unavailable", + "ext-mbstring": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8", + "ext-tokenizer": "Needed for fallback/polyfill parser support and file/line-based suppressions." + }, + "bin": [ + "phan", + "phan_client", + "tocheckstyle" + ], + "type": "project", + "autoload": { + "psr-4": { + "Phan\\": "src/Phan" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tyson Andre" + }, + { + "name": "Rasmus Lerdorf" + }, + { + "name": "Andrew S. Morrison" + } + ], + "description": "A static analyzer for PHP", + "keywords": [ + "analyzer", + "php", + "static" + ], + "support": { + "issues": "https://github.com/phan/phan/issues", + "source": "https://github.com/phan/phan/tree/4.0.2" + }, + "time": "2021-01-09T21:32:36+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.3" + }, + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "sabre/event", + "version": "5.1.2", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/event.git", + "reference": "c120bec57c17b6251a496efc82b732418b49d50a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/event/zipball/c120bec57c17b6251a496efc82b732418b49d50a", + "reference": "c120bec57c17b6251a496efc82b732418b49d50a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.16.1", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sabre\\Event\\": "lib/" + }, + "files": [ + "lib/coroutine.php", + "lib/Loop/functions.php", + "lib/Promise/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "sabre/event is a library for lightweight event-based programming", + "homepage": "http://sabre.io/event/", + "keywords": [ + "EventEmitter", + "async", + "coroutine", + "eventloop", + "events", + "hooks", + "plugin", + "promise", + "reactor", + "signal" + ], + "support": { + "forum": "https://groups.google.com/group/sabredav-discuss", + "issues": "https://github.com/sabre-io/event/issues", + "source": "https://github.com/fruux/sabre-event" + }, + "time": "2020-10-03T11:02:22+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozart/assert/issues", + "source": "https://github.com/webmozart/assert/tree/master" + }, + "time": "2020-07-08T17:02:28+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.4 || ^8.0" + }, + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php new file mode 100644 index 0000000..6479d45 --- /dev/null +++ b/src/ConfigProvider.php @@ -0,0 +1,68 @@ + $this->getDependencies(), + 'form_elements' => $this->getFormElements(), + ]; + } + + /** + * Returns the container dependencies + */ + public function getDependencies() : array + { + return [ + 'aliases' => [ + 'EventManager' => EventManager::class, + 'doctrine.hydrator' => Hydrator\DoctrineObject::class, + 'doctrine.entity_manager.orm_default' => EntityManager::class, + ], + 'invokables' => [ + EventManager::class => EventManager::class, + ], + 'factories' => [ + Hydrator\DoctrineObject::class => Hydrator\DoctrineObjectFactory::class, + EntityManager::class => EntityManagerFactory::class, + ], + ]; + } + + /** + * Returns the form dependencies + */ + public function getFormElements() : array + { + return [ + 'aliases' => [ + 'doctrine.object_select' => Form\Element\ObjectSelect::class, + ], + 'factories' => [ + Form\Element\ObjectSelect::class => Form\Element\ElementFactory::class, + ], + ]; + } +} diff --git a/src/Form/Element/ElementFactory.php b/src/Form/Element/ElementFactory.php new file mode 100644 index 0000000..0c82f8a --- /dev/null +++ b/src/Form/Element/ElementFactory.php @@ -0,0 +1,18 @@ +get('doctrine.entity_manager.orm_default'); + /** @var ObjectSelect|ObjectRadio|ObjectMultiCheckbox $element */ + $element = new $elementClass(); + $element->setOption('object_manager', $em); + return $element; + } +} diff --git a/src/Form/Element/Exception/InvalidRepositoryResultException.php b/src/Form/Element/Exception/InvalidRepositoryResultException.php new file mode 100644 index 0000000..9e9dd0f --- /dev/null +++ b/src/Form/Element/Exception/InvalidRepositoryResultException.php @@ -0,0 +1,9 @@ +proxy) { + $this->proxy = new Proxy(); + } + return $this->proxy; + } + + /** + * @param array|Traversable $options + * @return self + */ + public function setOptions($options): ObjectMultiCheckbox + { + $this->getProxy()->setOptions($options); + return parent::setOptions($options); + } + + /** + * @param string $key + * @param mixed $value + * @return self + */ + public function setOption($key, $value): ObjectMultiCheckbox + { + $this->getProxy()->setOptions([$key => $value]); + return parent::setOption($key, $value); + } + + /** + * {@inheritDoc} + */ + public function setValue($value) + { + if ($value instanceof Traversable) { + $value = ArrayUtils::iteratorToArray($value); + } elseif ($value == null) { + return parent::setValue([]); + } elseif (! is_array($value)) { + $value = (array)$value; + } + + return parent::setValue(array_map([$this->getProxy(), 'getValue'], $value)); + } + + /** + * {@inheritDoc} + */ + public function getValueOptions(): array + { + if (! empty($this->valueOptions)) { + return $this->valueOptions; + } + + $proxyValueOptions = $this->getProxy()->getValueOptions(); + + if (! empty($proxyValueOptions)) { + $this->setValueOptions($proxyValueOptions); + } + + return $this->valueOptions; + } +} diff --git a/src/Form/Element/ObjectRadio.php b/src/Form/Element/ObjectRadio.php new file mode 100644 index 0000000..e563d17 --- /dev/null +++ b/src/Form/Element/ObjectRadio.php @@ -0,0 +1,72 @@ +proxy) { + $this->proxy = new Proxy(); + } + return $this->proxy; + } + + /** + * @param array|Traversable $options + * @return self + */ + public function setOptions($options): ObjectRadio + { + $this->getProxy()->setOptions($options); + return parent::setOptions($options); + } + + /** + * @param string $key + * @param mixed $value + * @return self + */ + public function setOption($key, $value): ObjectRadio + { + $this->getProxy()->setOptions([$key => $value]); + return parent::setOption($key, $value); + } + + /** + * {@inheritDoc} + */ + public function setValue($value) + { + return parent::setValue($this->getProxy()->getValue($value)); + } + + /** + * {@inheritDoc} + */ + public function getValueOptions(): array + { + if (! empty($this->valueOptions)) { + return $this->valueOptions; + } + + $proxyValueOptions = $this->getProxy()->getValueOptions(); + + if (! empty($proxyValueOptions)) { + $this->setValueOptions($proxyValueOptions); + } + + return $this->valueOptions; + } +} diff --git a/src/Form/Element/ObjectSelect.php b/src/Form/Element/ObjectSelect.php new file mode 100644 index 0000000..c73e98e --- /dev/null +++ b/src/Form/Element/ObjectSelect.php @@ -0,0 +1,87 @@ +proxy) { + $this->proxy = new Proxy(); + } + return $this->proxy; + } + + /** + * @param array|Traversable $options + * @return self + */ + public function setOptions($options): ObjectSelect + { + $this->getProxy()->setOptions($options); + return parent::setOptions($options); + } + + /** + * @param string $key + * @param mixed $value + * @return self + */ + public function setOption($key, $value): ObjectSelect + { + $this->getProxy()->setOptions([$key => $value]); + return parent::setOption($key, $value); + } + + /** + * {@inheritDoc} + */ + public function setValue($value) + { + $multiple = $this->getAttribute('multiple'); + + if (true === $multiple || 'multiple' === $multiple) { + if ($value instanceof Traversable) { + $value = ArrayUtils::iteratorToArray($value); + } elseif ($value == null) { + return parent::setValue([]); + } elseif (! is_array($value)) { + $value = (array) $value; + } + + return parent::setValue(array_map([$this->getProxy(), 'getValue'], $value)); + } + + return parent::setValue($this->getProxy()->getValue($value)); + } + + /** + * {@inheritDoc} + */ + public function getValueOptions(): array + { + if (! empty($this->valueOptions)) { + return $this->valueOptions; + } + + $proxyValueOptions = $this->getProxy()->getValueOptions(); + + if (! empty($proxyValueOptions)) { + $this->setValueOptions($proxyValueOptions); + } + + return $this->valueOptions; + } +} diff --git a/src/Form/Element/Proxy.php b/src/Form/Element/Proxy.php new file mode 100644 index 0000000..48aecde --- /dev/null +++ b/src/Form/Element/Proxy.php @@ -0,0 +1,657 @@ +setObjectManager($options['object_manager']); + } + + if (isset($options['target_class'])) { + $this->setTargetClass($options['target_class']); + } + + if (isset($options['property'])) { + $this->setProperty($options['property']); + } + + if (isset($options['label_generator'])) { + $this->setLabelGenerator($options['label_generator']); + } + + if (isset($options['find_method'])) { + $this->setFindMethod($options['find_method']); + } + + if (isset($options['is_method'])) { + $this->setIsMethod($options['is_method']); + } + + if (isset($options['display_empty_item'])) { + $this->setDisplayEmptyItem($options['display_empty_item']); + } + + if (isset($options['empty_item_label'])) { + $this->setEmptyItemLabel($options['empty_item_label']); + } + + if (isset($options['option_attributes'])) { + $this->setOptionAttributes($options['option_attributes']); + } + + if (isset($options['optgroup_identifier'])) { + $this->setOptgroupIdentifier($options['optgroup_identifier']); + } + + if (isset($options['optgroup_default'])) { + $this->setOptgroupDefault($options['optgroup_default']); + } + } + + public function getValueOptions() + { + if (empty($this->valueOptions)) { + $this->loadValueOptions(); + } + + return $this->valueOptions; + } + + /** + * @return array|Traversable + */ + public function getObjects() + { + $this->loadObjects(); + + return $this->objects; + } + + /** + * Set the label for the empty option + * + * @param string $emptyItemLabel + * + * @return Proxy + */ + public function setEmptyItemLabel($emptyItemLabel): Proxy + { + $this->emptyItemLabel = $emptyItemLabel; + + return $this; + } + + /** + * @return string + */ + public function getEmptyItemLabel(): string + { + return $this->emptyItemLabel; + } + + /** + * @return array + */ + public function getOptionAttributes(): array + { + return $this->option_attributes; + } + + /** + * @param array $option_attributes + */ + public function setOptionAttributes(array $option_attributes) + { + $this->option_attributes = $option_attributes; + } + + /** + * Set a flag, whether to include the empty option at the beginning or not + * + * @param boolean $displayEmptyItem + * + * @return Proxy + */ + public function setDisplayEmptyItem(bool $displayEmptyItem): Proxy + { + $this->displayEmptyItem = $displayEmptyItem; + + return $this; + } + + /** + * @return boolean + */ + public function getDisplayEmptyItem(): bool + { + return $this->displayEmptyItem; + } + + /** + * Set the object manager + * + * @param ObjectManager $objectManager + * + * @return Proxy + */ + public function setObjectManager(ObjectManager $objectManager): Proxy + { + $this->objectManager = $objectManager; + + return $this; + } + + /** + * Get the object manager + * + * @return ObjectManager + */ + public function getObjectManager(): ObjectManager + { + return $this->objectManager; + } + + /** + * Set the FQCN of the target object + * + * @param string $targetClass + * + * @return Proxy + */ + public function setTargetClass(string $targetClass): Proxy + { + $this->targetClass = $targetClass; + + return $this; + } + + /** + * Get the target class + * + * @return string + */ + public function getTargetClass(): string + { + return $this->targetClass; + } + + /** + * Set the property to use as the label in the options + * + * @param string $property + * + * @return Proxy + */ + public function setProperty(string $property): Proxy + { + $this->property = $property; + + return $this; + } + + /** + * @return string + */ + public function getProperty(): string + { + return $this->property; + } + + /** + * Set the label generator callable that is responsible for generating labels for the items in the collection + * + * @param callable $callable A callable used to create a label based off of an Entity + * + * @return void + * @throws InvalidArgumentException + */ + public function setLabelGenerator(callable $callable) + { + if (! is_callable($callable)) { + throw new InvalidArgumentException( + 'Property "label_generator" needs to be a callable function or a \Closure' + ); + } + + $this->labelGenerator = $callable; + } + + /** + * @return callable|null + */ + public function getLabelGenerator(): ?callable + { + return $this->labelGenerator; + } + + /** + * @return string|null + */ + public function getOptgroupIdentifier(): ?string + { + return $this->optgroupIdentifier; + } + + /** + * @param string $optgroupIdentifier + */ + public function setOptgroupIdentifier(string $optgroupIdentifier) + { + $this->optgroupIdentifier = (string) $optgroupIdentifier; + } + + /** + * @return string|null + */ + public function getOptgroupDefault(): ?string + { + return $this->optgroupDefault; + } + + /** + * @param string $optgroupDefault + */ + public function setOptgroupDefault(string $optgroupDefault) + { + $this->optgroupDefault = (string) $optgroupDefault; + } + + /** + * Set if the property is a method to use as the label in the options + * + * @param boolean $method + * + * @return Proxy + */ + public function setIsMethod(bool $method): Proxy + { + $this->isMethod = (bool) $method; + + return $this; + } + + /** + * @return mixed + */ + public function getIsMethod(): ?bool + { + return $this->isMethod; + } + + /** Set the findMethod property to specify the method to use on repository + * + * @param array $findMethod + * + * @return Proxy + */ + public function setFindMethod(array $findMethod): Proxy + { + $this->findMethod = $findMethod; + + return $this; + } + + /** + * Get findMethod definition + * + * @return array + */ + public function getFindMethod(): array + { + return $this->findMethod; + } + + /** + * @param $targetEntity + * + * @return string|null + */ + protected function generateLabel($targetEntity): ?string + { + if (null === ($labelGenerator = $this->getLabelGenerator())) { + return null; + } + + return call_user_func($labelGenerator, $targetEntity); + } + + /** + * @param $value + * + * @return array|mixed|object + * @throws RuntimeException + */ + public function getValue($value) + { + if (! ($om = $this->getObjectManager())) { + throw new RuntimeException('No object manager was set'); + } + + if (! ($targetClass = $this->getTargetClass())) { + throw new RuntimeException('No target class was set'); + } + + $metadata = $om->getClassMetadata($targetClass); + + if (is_object($value)) { + if ($value instanceof Collection) { + $data = []; + + foreach ($value as $object) { + $values = $metadata->getIdentifierValues($object); + $data[] = array_shift($values); + } + + $value = $data; + } else { + $metadata = $om->getClassMetadata(get_class($value)); + $identifier = $metadata->getIdentifierFieldNames(); + + // TODO: handle composite (multiple) identifiers + if (null !== $identifier && count($identifier) > 1) { + //$value = $key; + } else { + $value = current($metadata->getIdentifierValues($value)); + } + } + } + + return $value; + } + + /** + * Load objects + * + * @return void + * @throws Exception\InvalidRepositoryResultException + * @throws ReflectionException + * @throws RuntimeException + * @throws \Exception + */ + protected function loadObjects() + { + if (! empty($this->objects)) { + return; + } + + $findMethod = (array) $this->getFindMethod(); + + if (! $findMethod) { + $findMethodName = 'findAll'; + $repository = $this->objectManager->getRepository($this->targetClass); + $objects = $repository->findAll(); + } else { + if (! isset($findMethod['name'])) { + throw new RuntimeException('No method name was set'); + } + $findMethodName = $findMethod['name']; + $findMethodParams = isset($findMethod['params']) ? array_change_key_case($findMethod['params']) : []; + $repository = $this->objectManager->getRepository($this->targetClass); + + if (! method_exists($repository, $findMethodName)) { + throw new RuntimeException( + sprintf( + 'Method "%s" could not be found in repository "%s"', + $findMethodName, + get_class($repository) + ) + ); + } + + $r = new ReflectionMethod($repository, $findMethodName); + $args = []; + + foreach ($r->getParameters() as $param) { + if (array_key_exists(strtolower($param->getName()), $findMethodParams)) { + $args[] = $findMethodParams[strtolower($param->getName())]; + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } elseif (! $param->isOptional()) { + throw new RuntimeException( + sprintf( + 'Required parameter "%s" with no default value for method "%s" in repository "%s"' + . ' was not provided', + $param->getName(), + $findMethodName, + get_class($repository) + ) + ); + } + } + $objects = $r->invokeArgs($repository, $args); + } + + $this->guardForArrayOrTraversable( + $objects, + sprintf('%s::%s() return value', get_class($repository), $findMethodName), + 'DoctrineModule\Form\Element\Exception\InvalidRepositoryResultException' + ); + + $this->objects = $objects; + } + + /** + * Load value options + * + * @throws RuntimeException + * @return void + */ + protected function loadValueOptions() + { + if (! ($om = $this->objectManager)) { + throw new RuntimeException('No object manager was set'); + } + + if (! ($targetClass = $this->targetClass)) { + throw new RuntimeException('No target class was set'); + } + + $metadata = $om->getClassMetadata($targetClass); + $identifier = $metadata->getIdentifierFieldNames(); + $objects = $this->getObjects(); + $options = []; + $optionAttributes = []; + + if ($this->displayEmptyItem) { + $options[''] = $this->getEmptyItemLabel(); + } + + foreach ($objects as $key => $object) { + if (null !== ($generatedLabel = $this->generateLabel($object))) { + $label = $generatedLabel; + } elseif ($property = $this->property) { + if ($this->isMethod == false && ! $metadata->hasField($property)) { + throw new RuntimeException( + sprintf( + 'Property "%s" could not be found in object "%s"', + $property, + $targetClass + ) + ); + } + + $inflector = InflectorFactory::create()->build(); + $getter = 'get' . $inflector->classify($property); + + if (! is_callable([$object, $getter])) { + throw new RuntimeException( + sprintf('Method "%s::%s" is not callable', $this->targetClass, $getter) + ); + } + + $label = $object->{$getter}(); + } else { + if (! is_callable([$object, '__toString'])) { + throw new RuntimeException( + sprintf( + '%s must have a "__toString()" method defined if you have not set a property' + . ' or method to use.', + $targetClass + ) + ); + } + + $label = (string) $object; + } + + if (null !== $identifier && count($identifier) > 1) { + $value = $key; + } else { + $value = current($metadata->getIdentifierValues($object)); + } + + foreach ($this->getOptionAttributes() as $optionKey => $optionValue) { + if (is_string($optionValue)) { + $optionAttributes[$optionKey] = $optionValue; + + continue; + } + + if (is_callable($optionValue)) { + $callableValue = call_user_func($optionValue, $object); + $optionAttributes[$optionKey] = (string) $callableValue; + + continue; + } + + throw new RuntimeException( + sprintf( + 'Parameter "option_attributes" expects an array of key => value where value is of type' + . '"string" or "callable". Value of type "%s" found.', + gettype($optionValue) + ) + ); + } + + // If no optgroup_identifier has been configured, apply default handling and continue + if (is_null($this->getOptgroupIdentifier())) { + $options[] = ['label' => $label, 'value' => $value, 'attributes' => $optionAttributes]; + + continue; + } + + // optgroup_identifier found, handle grouping + $inflector = InflectorFactory::create()->build(); + $optgroupGetter = 'get' . $inflector->classify($this->getOptgroupIdentifier()); + + if (! is_callable([$object, $optgroupGetter])) { + throw new RuntimeException( + sprintf('Method "%s::%s" is not callable', $this->targetClass, $optgroupGetter) + ); + } + + $optgroup = $object->{$optgroupGetter}(); + + // optgroup_identifier contains a valid group-name. Handle default grouping. + if (false === is_null($optgroup) && trim($optgroup) !== '') { + $options[$optgroup]['label'] = $optgroup; + $options[$optgroup]['options'][] = [ + 'label' => $label, + 'value' => $value, + 'attributes' => $optionAttributes, + ]; + + continue; + } + + $optgroupDefault = $this->getOptgroupDefault(); + + // No optgroup_default has been provided. Line up without a group + if (is_null($optgroupDefault)) { + $options[] = ['label' => $label, 'value' => $value, 'attributes' => $optionAttributes]; + + continue; + } + + // Line up entry with optgroup_default + $options[$optgroupDefault]['label'] = $optgroupDefault; + $options[$optgroupDefault]['options'][] = [ + 'label' => $label, + 'value' => $value, + 'attributes' => $optionAttributes, + ]; + } + + $this->valueOptions = $options; + } +} diff --git a/src/Hydrator/DoctrineObject.php b/src/Hydrator/DoctrineObject.php new file mode 100755 index 0000000..8444419 --- /dev/null +++ b/src/Hydrator/DoctrineObject.php @@ -0,0 +1,621 @@ +. + */ + +namespace DoctrineMezzioModule\Hydrator; + +use DateTime; +use DateTimeImmutable; +use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\Inflector\InflectorFactory; +use Exception; +use InvalidArgumentException; +use ReflectionException; +use RuntimeException; +use Traversable; +use Laminas\Stdlib\ArrayUtils; +use Laminas\Hydrator\AbstractHydrator; +use Laminas\Hydrator\Filter\FilterProviderInterface; + +/** + * This hydrator has been completely refactored for DoctrineModule 0.7.0. It provides an easy and powerful way + * of extracting/hydrator objects in Doctrine, by handling most associations types. + * + * Starting from DoctrineModule 0.8.0, the hydrator can be used multiple times with different objects + * + * @license MIT + * @link http://www.doctrine-project.org/ + * @since 0.7.0 + * @author Michael Gallego + */ +class DoctrineObject extends AbstractHydrator +{ + /** + * @var ObjectManager + */ + protected ObjectManager $objectManager; + + /** + * @var ClassMetadata + */ + protected ClassMetadata $metadata; + + /** + * @var bool + */ + protected bool $byValue = true; + + + /** + * Constructor + * + * @param ObjectManager $objectManager The ObjectManager to use + * @param bool $byValue If set to true, hydrator will always use entity's public API + */ + public function __construct(ObjectManager $objectManager, $byValue = true) + { + $this->objectManager = $objectManager; + $this->byValue = (bool) $byValue; + } + + /** + * Extract values from an object + * + * @param object $object + * @return array + * @throws ReflectionException + */ + public function extract(object $object): array + { + $this->prepare($object); + + if ($this->byValue) { + return $this->extractByValue($object); + } + + return $this->extractByReference($object); + } + + /** + * Hydrate $object with the provided $data. + * + * @param array $data + * @param object $object + * @return object + * @throws ReflectionException + * @throws Exception + */ + public function hydrate(array $data, object $object): object + { + $this->prepare($object); + + if ($this->byValue) { + return $this->hydrateByValue($data, $object); + } + + return $this->hydrateByReference($data, $object); + } + + /** + * Prepare the hydrator by adding strategies to every collection valued associations + * + * @param object $object + * @return void + */ + protected function prepare(object $object) + { + $this->metadata = $this->objectManager->getClassMetadata(get_class($object)); + $this->prepareStrategies(); + } + + /** + * Prepare strategies before the hydrator is used + * + * @throws InvalidArgumentException + * @return void + */ + protected function prepareStrategies() + { + $associations = $this->metadata->getAssociationNames(); + + foreach ($associations as $association) { + if ($this->metadata->isCollectionValuedAssociation($association)) { + // Add a strategy if the association has none set by user + if (!$this->hasStrategy($association)) { + if ($this->byValue) { + $this->addStrategy($association, new Strategy\AllowRemoveByValue()); + } else { + $this->addStrategy($association, new Strategy\AllowRemoveByReference()); + } + } + + $strategy = $this->getStrategy($association); + + if (!$strategy instanceof Strategy\AbstractCollectionStrategy) { + throw new InvalidArgumentException( + sprintf( + 'Strategies used for collections valued associations must inherit from ' + . 'Strategy\AbstractCollectionStrategy, %s given', + get_class($strategy) + ) + ); + } + + $strategy->setCollectionName($association) + ->setClassMetadata($this->metadata); + } + } + } + + /** + * Extract values from an object using a by-value logic (this means that it uses the entity + * API, in this case, getters) + * + * @param object $object + * @return array + * @throws RuntimeException + */ + protected function extractByValue(object $object): array + { + $fieldNames = array_merge($this->metadata->getFieldNames(), $this->metadata->getAssociationNames()); + $methods = get_class_methods($object); + $filter = $object instanceof FilterProviderInterface + ? $object->getFilter() + : $this->filterComposite; + + $data = []; + foreach ($fieldNames as $fieldName) { + if ($filter && !$filter->filter($fieldName)) { + continue; + } + + $inflector = InflectorFactory::create()->build(); + $getter = 'get' . $inflector->classify($fieldName); + $isser = 'is' . $inflector->classify($fieldName); + + $dataFieldName = $this->computeExtractFieldName($fieldName); + if (in_array($getter, $methods)) { + $data[$dataFieldName] = $this->extractValue($fieldName, $object->$getter(), $object); + } elseif (in_array($isser, $methods)) { + $data[$dataFieldName] = $this->extractValue($fieldName, $object->$isser(), $object); + } elseif (substr($fieldName, 0, 2) === 'is' + && ctype_upper(substr($fieldName, 2, 1)) + && in_array($fieldName, $methods)) { + $data[$dataFieldName] = $this->extractValue($fieldName, $object->$fieldName(), $object); + } + + // Unknown fields are ignored + } + + return $data; + } + + /** + * Extract values from an object using a by-reference logic (this means that values are + * directly fetched without using the public API of the entity, in this case, getters) + * + * @param object $object + * @return array + * @throws ReflectionException + */ + protected function extractByReference(object $object): array + { + $fieldNames = array_merge($this->metadata->getFieldNames(), $this->metadata->getAssociationNames()); + $refl = $this->metadata->getReflectionClass(); + $filter = $object instanceof FilterProviderInterface + ? $object->getFilter() + : $this->filterComposite; + + $data = []; + foreach ($fieldNames as $fieldName) { + if ($filter && !$filter->filter($fieldName)) { + continue; + } + $reflProperty = $refl->getProperty($fieldName); + $reflProperty->setAccessible(true); + + $dataFieldName = $this->computeExtractFieldName($fieldName); + $data[$dataFieldName] = $this->extractValue($fieldName, $reflProperty->getValue($object), $object); + } + + return $data; + } + + /** + * Hydrate the object using a by-value logic (this means that it uses the entity API, in this + * case, setters) + * + * @param array $data + * @param object $object + * @return object + * @throws RuntimeException|ReflectionException + * @throws Exception + */ + protected function hydrateByValue(array $data, object $object): object + { + $tryObject = $this->tryConvertArrayToObject($data, $object); + $metadata = $this->metadata; + + if (is_object($tryObject)) { + $object = $tryObject; + } + + foreach ($data as $field => $value) { + $field = $this->computeHydrateFieldName($field); + $value = $this->handleTypeConversions($value, $metadata->getTypeOfField($field)); + $inflector = InflectorFactory::create()->build(); + $setter = 'set' . $inflector->classify($field); + + if ($metadata->hasAssociation($field)) { + $target = $metadata->getAssociationTargetClass($field); + + if ($metadata->isSingleValuedAssociation($field)) { + if (! method_exists($object, $setter)) { + continue; + } + + $value = $this->toOne($target, $this->hydrateValue($field, $value, $data)); + + if (null === $value + && !current($metadata->getReflectionClass()->getMethod($setter)->getParameters())->allowsNull() + ) { + continue; + } + + $object->$setter($value); + } elseif ($metadata->isCollectionValuedAssociation($field)) { + $this->toMany($object, $field, $target, $value); + } + } else { + if (! method_exists($object, $setter)) { + continue; + } + + $object->$setter($this->hydrateValue($field, $value, $data)); + } + } + + return $object; + } + + /** + * Hydrate the object using a by-reference logic (this means that values are modified directly without + * using the public API, in this case setters, and hence override any logic that could be done in those + * setters) + * + * @param array $data + * @param object $object + * @return object + * @throws Exception + */ + protected function hydrateByReference(array $data, object $object): object + { + $tryObject = $this->tryConvertArrayToObject($data, $object); + $metadata = $this->metadata; + $refl = $metadata->getReflectionClass(); + + if (is_object($tryObject)) { + $object = $tryObject; + } + + foreach ($data as $field => $value) { + $field = $this->computeHydrateFieldName($field); + + // Ignore unknown fields + if (!$refl->hasProperty($field)) { + continue; + } + + $value = $this->handleTypeConversions($value, $metadata->getTypeOfField($field)); + $reflProperty = $refl->getProperty($field); + $reflProperty->setAccessible(true); + + if ($metadata->hasAssociation($field)) { + $target = $metadata->getAssociationTargetClass($field); + + if ($metadata->isSingleValuedAssociation($field)) { + $value = $this->toOne($target, $this->hydrateValue($field, $value, $data)); + $reflProperty->setValue($object, $value); + } elseif ($metadata->isCollectionValuedAssociation($field)) { + $this->toMany($object, $field, $target, $value); + } + } else { + $reflProperty->setValue($object, $this->hydrateValue($field, $value, $data)); + } + } + + return $object; + } + + /** + * This function tries, given an array of data, to convert it to an object if the given array contains + * an identifier for the object. This is useful in a context of updating existing entities, without ugly + * tricks like setting manually the existing id directly into the entity + * + * @param array $data The data that may contain identifiers keys + * @param object $object + * @return object + */ + protected function tryConvertArrayToObject(array $data, object $object): ?object + { + $metadata = $this->metadata; + $identifierNames = $metadata->getIdentifierFieldNames(); + $identifierValues = []; + + if (empty($identifierNames)) { + return $object; + } + + foreach ($identifierNames as $identifierName) { + if (!isset($data[$identifierName])) { + return $object; + } + + $identifierValues[$identifierName] = $data[$identifierName]; + } + + return $this->find($identifierValues, $metadata->getName()); + } + + /** + * Handle ToOne associations + * + * When $value is an array but is not the $target's identifiers, $value is + * most likely an array of fieldset data. The identifiers will be determined + * and a target instance will be initialized and then hydrated. The hydrated + * target will be returned. + * + * @param string $target + * @param mixed $value + * @return object + * @throws ReflectionException + */ + protected function toOne(string $target, $value): ?object + { + $metadata = $this->objectManager->getClassMetadata($target); + + if (is_array($value) && array_keys($value) != $metadata->getIdentifier()) { + // $value is most likely an array of fieldset data + $identifiers = array_intersect_key( + $value, + array_flip($metadata->getIdentifier()) + ); + $object = $this->find($identifiers, $target) ?: new $target; + return $this->hydrate($value, $object); + } + + return $this->find($value, $target); + } + + /** + * Handle ToMany associations. In proper Doctrine design, Collections should not be swapped, so + * collections are always handled by reference. Internally, every collection is handled using specials + * strategies that inherit from AbstractCollectionStrategy class, and that add or remove elements but without + * changing the collection of the object + * + * @param object $object + * @param mixed $collectionName + * @param string $target + * @param mixed $values + * + * @return void + * @throws InvalidArgumentException|ReflectionException + */ + protected function toMany(object $object, $collectionName, string $target, $values) + { + $metadata = $this->objectManager->getClassMetadata(ltrim($target, '\\')); + $identifier = $metadata->getIdentifier(); + + if (!is_array($values) && !$values instanceof Traversable) { + $values = (array)$values; + } + + $collection = []; + + // If the collection contains identifiers, fetch the objects from database + foreach ($values as $value) { + if ($value instanceof $target) { + // assumes modifications have already taken place in object + $collection[] = $value; + continue; + } elseif (empty($value)) { + // assumes no id and retrieves new $target + $collection[] = $this->find($value, $target); + continue; + } + + $find = []; + if (is_array($identifier)) { + foreach ($identifier as $field) { + switch (gettype($value)) { + case 'object': + $getter = 'get' . ucfirst($field); + if (method_exists($value, $getter)) { + $find[$field] = $value->$getter(); + } elseif (property_exists($value, $field)) { + $find[$field] = $value->$field; + } + break; + case 'array': + if (array_key_exists($field, $value) && $value[$field] != null) { + $find[$field] = $value[$field]; + unset($value[$field]); // removed identifier from persistable data + } + break; + default: + $find[$field] = $value; + break; + } + } + } + + if (!empty($find) && $found = $this->find($find, $target)) { + $collection[] = (is_array($value)) ? $this->hydrate($value, $found) : $found; + } else { + $collection[] = (is_array($value)) ? $this->hydrate($value, new $target) : new $target; + } + } + + $collection = array_filter( + $collection, + function ($item) { + return null !== $item; + } + ); + + // Set the object so that the strategy can extract the Collection from it + + /** @var Strategy\AbstractCollectionStrategy $collectionStrategy */ + $collectionStrategy = $this->getStrategy($collectionName); + $collectionStrategy->setObject($object); // @phan-suppress-current-line PhanUndeclaredMethod + + // We could directly call hydrate method from the strategy, but if people want to override + // hydrateValue function, they can do it and do their own stuff + $this->hydrateValue($collectionName, $collection, $values); + } + + /** + * Handle various type conversions that should be supported natively by Doctrine (like DateTime) + * + * @param mixed $value + * @param string $typeOfField + * @return DateTime|DateTimeImmutable|null + * @throws Exception + */ + protected function handleTypeConversions($value, string $typeOfField) + { + switch ($typeOfField) { + case 'datetimetz': + case 'datetime': + case 'time': + case 'date': + if ('' === $value) { + return null; + } + + if (is_int($value)) { + $dateTime = new DateTime(); + $dateTime->setTimestamp($value); + $value = $dateTime; + } elseif (is_string($value)) { + $value = new DateTime($value); + } + + break; + case 'datetimetz_immutable': + case 'datetime_immutable': + case 'time_immutable': + case 'date_immutable': + if ('' === $value) { + return null; + } + + if (is_int($value)) { + $dateTime = new DateTimeImmutable(); + $dateTime->setTimestamp($value); + $value = $dateTime; + } elseif (is_string($value)) { + $value = new DateTimeImmutable($value); + } + + break; + default: + } + + return $value; + } + + /** + * Find an object by a given target class and identifier + * + * @param mixed $identifiers + * @param string $targetClass + * + * @return object|null + */ + protected function find($identifiers, string $targetClass): ?object + { + if ($identifiers instanceof $targetClass) { + return $identifiers; + } + + if ($this->isNullIdentifier($identifiers)) { + return null; + } + + return $this->objectManager->find($targetClass, $identifiers); + } + + /** + * Verifies if a provided identifier is to be considered null + * + * @param mixed $identifier + * + * @return bool + */ + private function isNullIdentifier($identifier): bool + { + if (null === $identifier) { + return true; + } + + if ($identifier instanceof Traversable || is_array($identifier)) { + $nonNullIdentifiers = array_filter( + ArrayUtils::iteratorToArray($identifier), + function ($value) { + return null !== $value; + } + ); + + return empty($nonNullIdentifiers); + } + + return false; + } + + /** + * Applies the naming strategy if there is one set + * + * @param string $field + * + * @return string + */ + protected function computeHydrateFieldName(string $field): string + { + if ($this->hasNamingStrategy()) { + $field = $this->getNamingStrategy()->hydrate($field); + } + return $field; + } + + /** + * Applies the naming strategy if there is one set + * + * @param string $field + * + * @return string + */ + protected function computeExtractFieldName(string $field): string + { + if ($this->hasNamingStrategy()) { + $field = $this->getNamingStrategy()->extract($field); + } + return $field; + } +} diff --git a/src/Hydrator/DoctrineObjectFactory.php b/src/Hydrator/DoctrineObjectFactory.php new file mode 100644 index 0000000..f9ef8b4 --- /dev/null +++ b/src/Hydrator/DoctrineObjectFactory.php @@ -0,0 +1,15 @@ +get('doctrine.entity_manager.orm_default'); + return new DoctrineObject($em); + } +} diff --git a/src/Hydrator/Filter/PropertyName.php b/src/Hydrator/Filter/PropertyName.php new file mode 100644 index 0000000..2cd1c8c --- /dev/null +++ b/src/Hydrator/Filter/PropertyName.php @@ -0,0 +1,66 @@ +. + */ + +namespace DoctrineMezzioModule\Hydrator\Filter; + +use Laminas\Hydrator\Filter\FilterInterface; + +/** + * Provides a filter to restrict returned fields by whitelisting or + * blacklisting property names. + * + * @license MIT + * @link http://www.doctrine-project.org/ + * @author Liam O'Boyle + */ +class PropertyName implements FilterInterface +{ + /** + * The propteries to exclude. + * + * @var array + */ + protected $properties = []; + + /** + * Either an exclude or an include. + * + * @var bool + */ + protected $exclude = null; + + /** + * @param string|array $properties The properties to exclude or include. + * @param bool $exclude If the method should be excluded + */ + public function __construct($properties, $exclude = true) + { + $this->exclude = $exclude; + $this->properties = is_array($properties) + ? $properties + : [$properties]; + } + + public function filter(string $property): bool + { + return in_array($property, $this->properties) + ? !$this->exclude + : $this->exclude; + } +} diff --git a/src/Hydrator/Strategy/AbstractCollectionStrategy.php b/src/Hydrator/Strategy/AbstractCollectionStrategy.php new file mode 100644 index 0000000..c65b033 --- /dev/null +++ b/src/Hydrator/Strategy/AbstractCollectionStrategy.php @@ -0,0 +1,190 @@ +. + */ + +namespace DoctrineMezzioModule\Hydrator\Strategy; + +use InvalidArgumentException; +use Doctrine\Common\Collections\Collection; +use Doctrine\Persistence\Mapping\ClassMetadata; +use Laminas\Hydrator\Strategy\StrategyInterface; + +/** + * @license MIT + * @link http://www.doctrine-project.org/ + * @since 0.7.0 + * @author Michael Gallego + */ +abstract class AbstractCollectionStrategy implements StrategyInterface +{ + /** + * @var string + */ + protected $collectionName; + + /** + * @var ClassMetadata + */ + protected $metadata; + + /** + * @var object + */ + protected $object; + + + /** + * Set the name of the collection + * + * @param string $collectionName + * @return AbstractCollectionStrategy + */ + public function setCollectionName(string $collectionName): AbstractCollectionStrategy + { + $this->collectionName = (string) $collectionName; + return $this; + } + + /** + * Get the name of the collection + * + * @return string + */ + public function getCollectionName(): string + { + return $this->collectionName; + } + + /** + * Set the class metadata + * + * @param ClassMetadata $classMetadata + * @return AbstractCollectionStrategy + */ + public function setClassMetadata(ClassMetadata $classMetadata): AbstractCollectionStrategy + { + $this->metadata = $classMetadata; + return $this; + } + + /** + * Get the class metadata + * + * @return ClassMetadata + */ + public function getClassMetadata(): ClassMetadata + { + return $this->metadata; + } + + /** + * Set the object + * + * @param object $object + * + * @return AbstractCollectionStrategy + * @throws InvalidArgumentException + */ + public function setObject(object $object): AbstractCollectionStrategy + { + if (!is_object($object)) { + throw new InvalidArgumentException( + sprintf('The parameter given to setObject method of %s class is not an object', get_called_class()) + ); + } + + $this->object = $object; + return $this; + } + + /** + * Get the object + * + * @return object + */ + public function getObject(): object + { + return $this->object; + } + + /** + * {@inheritDoc} + */ + public function extract($value, $object = null) + { + return $value; + } + + /** + * Return the collection by value (using the public API) + * + * @throws \InvalidArgumentException + * + * @return Collection + */ + protected function getCollectionFromObjectByValue(): Collection + { + $object = $this->getObject(); + $getter = 'get' . ucfirst($this->getCollectionName()); + + if (!method_exists($object, $getter)) { + throw new InvalidArgumentException( + sprintf( + 'The getter %s to access collection %s in object %s does not exist', + $getter, + $this->getCollectionName(), + get_class($object) + ) + ); + } + + return $object->$getter(); + } + + /** + * Return the collection by reference (not using the public API) + * + * @return Collection + * @throws \ReflectionException + */ + protected function getCollectionFromObjectByReference(): Collection + { + $object = $this->getObject(); + $refl = $this->getClassMetadata()->getReflectionClass(); + $reflProperty = $refl->getProperty($this->getCollectionName()); + + $reflProperty->setAccessible(true); + + return $reflProperty->getValue($object); + } + + + /** + * This method is used internally by array_udiff to check if two objects are equal, according to their + * SPL hash. This is needed because the native array_diff only compare strings + * + * @param object $a + * @param object $b + * + * @return int + */ + protected function compareObjects(object $a, object $b): int + { + return strcmp(spl_object_hash($a), spl_object_hash($b)); + } +} diff --git a/src/Hydrator/Strategy/AllowRemoveByReference.php b/src/Hydrator/Strategy/AllowRemoveByReference.php new file mode 100644 index 0000000..b457e40 --- /dev/null +++ b/src/Hydrator/Strategy/AllowRemoveByReference.php @@ -0,0 +1,61 @@ +. + */ + +namespace DoctrineMezzioModule\Hydrator\Strategy; + +use Doctrine\Common\Collections\Collection; + +/** + * When this strategy is used for Collections, if the new collection does not contain elements that are present in + * the original collection, then this strategy remove elements from the original collection. For instance, if the + * collection initially contains elements A and B, and that the new collection contains elements B and C, then the + * final collection will contain elements B and C (while element A will be asked to be removed). + * + * This strategy is by reference, this means it won't use public API to add/remove elements to the collection + * + * @license MIT + * @link http://www.doctrine-project.org/ + * @since 0.7.0 + * @author Michael Gallego + */ +class AllowRemoveByReference extends AbstractCollectionStrategy +{ + /** + * {@inheritDoc} + * @throws \ReflectionException + */ + public function hydrate($value, $data = null): Collection + { + $collection = $this->getCollectionFromObjectByReference(); + $collectionArray = $collection->toArray(); + + $toAdd = array_udiff($value, $collectionArray, [$this, 'compareObjects']); + $toRemove = array_udiff($collectionArray, $value, [$this, 'compareObjects']); + + foreach ($toAdd as $element) { + $collection->add($element); + } + + foreach ($toRemove as $element) { + $collection->removeElement($element); + } + + return $collection; + } +} diff --git a/src/Hydrator/Strategy/AllowRemoveByValue.php b/src/Hydrator/Strategy/AllowRemoveByValue.php new file mode 100644 index 0000000..e783c88 --- /dev/null +++ b/src/Hydrator/Strategy/AllowRemoveByValue.php @@ -0,0 +1,77 @@ +. + */ + +namespace DoctrineMezzioModule\Hydrator\Strategy; + +use Doctrine\Common\Collections\Collection; +use LogicException; +use Doctrine\Common\Collections\ArrayCollection; + +/** + * When this strategy is used for Collections, if the new collection does not contain elements that are present in + * the original collection, then this strategy remove elements from the original collection. For instance, if the + * collection initially contains elements A and B, and that the new collection contains elements B and C, then the + * final collection will contain elements B and C (while element A will be asked to be removed). + * + * This strategy is by value, this means it will use the public API (in this case, adder and remover) + * + * @license MIT + * @link http://www.doctrine-project.org/ + * @since 0.7.0 + * @author Michael Gallego + */ +class AllowRemoveByValue extends AbstractCollectionStrategy +{ + /** + * {@inheritDoc} + */ + public function hydrate($value, $data = null) + { + // AllowRemove strategy need "adder" and "remover" + $adder = 'add' . ucfirst($this->collectionName); + $remover = 'remove' . ucfirst($this->collectionName); + + if (!method_exists($this->object, $adder) || !method_exists($this->object, $remover)) { + throw new LogicException( + sprintf( + 'AllowRemove strategy for DoctrineModule hydrator requires both %s and %s to be defined in %s + entity domain code, but one or both seem to be missing', + $adder, + $remover, + get_class($this->object) + ) + ); + } + + $collection = $this->getCollectionFromObjectByValue(); + + if ($collection instanceof Collection) { + $collection = $collection->toArray(); + } + + /** @var array $collection */ + $toAdd = new ArrayCollection(array_udiff($value, $collection, [$this, 'compareObjects'])); + $toRemove = new ArrayCollection(array_udiff($collection, $value, [$this, 'compareObjects'])); + + $this->object->$adder($toAdd); + $this->object->$remover($toRemove); + + return $collection; + } +} diff --git a/src/Hydrator/Strategy/DisallowRemoveByReference.php b/src/Hydrator/Strategy/DisallowRemoveByReference.php new file mode 100644 index 0000000..0640002 --- /dev/null +++ b/src/Hydrator/Strategy/DisallowRemoveByReference.php @@ -0,0 +1,54 @@ +. + */ + +namespace DoctrineMezzioModule\Hydrator\Strategy; + +/** + * When this strategy is used for Collections, if the new collection does not contain elements that are present in + * the original collection, then this strategy will not remove those elements. At most, it will add new elements. For + * instance, if the collection initially contains elements A and B, and that the new collection contains elements B + * and C, then the final collection will contain elements A, B and C. + * + * This strategy is by reference, this means it won't use the public API to remove elements + * + * @license MIT + * @link http://www.doctrine-project.org/ + * @since 0.7.0 + * @author Michael Gallego + */ +class DisallowRemoveByReference extends AbstractCollectionStrategy +{ + /** + * {@inheritDoc} + * @throws \ReflectionException + */ + public function hydrate($value, $data = null) + { + $collection = $this->getCollectionFromObjectByReference(); + $collectionArray = $collection->toArray(); + + $toAdd = array_udiff($value, $collectionArray, [$this, 'compareObjects']); + + foreach ($toAdd as $element) { + $collection->add($element); + } + + return $collection; + } +} diff --git a/src/Hydrator/Strategy/DisallowRemoveByValue.php b/src/Hydrator/Strategy/DisallowRemoveByValue.php new file mode 100644 index 0000000..e173e00 --- /dev/null +++ b/src/Hydrator/Strategy/DisallowRemoveByValue.php @@ -0,0 +1,72 @@ +. + */ + +namespace DoctrineMezzioModule\Hydrator\Strategy; + +use Doctrine\Common\Collections\Collection; +use LogicException; +use Doctrine\Common\Collections\ArrayCollection; + +/** + * When this strategy is used for Collections, if the new collection does not contain elements that are present in + * the original collection, then this strategy will not remove those elements. At most, it will add new elements. For + * instance, if the collection initially contains elements A and B, and that the new collection contains elements B + * and C, then the final collection will contain elements A, B and C. + * + * This strategy is by value, this means it will use the public API (in this case, remover) + * + * @license MIT + * @link http://www.doctrine-project.org/ + * @since 0.7.0 + * @author Michael Gallego + */ +class DisallowRemoveByValue extends AbstractCollectionStrategy +{ + /** + * {@inheritDoc} + */ + public function hydrate($value, $data = null) + { + // AllowRemove strategy need "adder" + $adder = 'add' . ucfirst($this->collectionName); + + if (!method_exists($this->object, $adder)) { + throw new LogicException( + sprintf( + 'DisallowRemove strategy for DoctrineModule hydrator requires %s to + be defined in %s entity domain code, but it seems to be missing', + $adder, + get_class($this->object) + ) + ); + } + + $collection = $this->getCollectionFromObjectByValue(); + + if ($collection instanceof Collection) { + $collection = $collection->toArray(); + } + + $toAdd = new ArrayCollection(array_udiff($value, $collection, [$this, 'compareObjects'])); + + $this->object->$adder($toAdd); + + return $collection; + } +} diff --git a/src/Validator/NoObjectExists.php b/src/Validator/NoObjectExists.php new file mode 100644 index 0000000..89dee39 --- /dev/null +++ b/src/Validator/NoObjectExists.php @@ -0,0 +1,43 @@ + + */ +class NoObjectExists extends ObjectExists +{ + /** + * Error constants + */ + const ERROR_OBJECT_FOUND = 'objectFound'; + + /** + * @var array Message templates + */ + protected $messageTemplates = [ + self::ERROR_OBJECT_FOUND => "An object matching '%value%' was found", + ]; + + /** + * {@inheritDoc} + */ + public function isValid($value): bool + { + $cleanedValue = $this->cleanSearchValue($value); + $match = $this->objectRepository->findOneBy($cleanedValue); + + if (is_object($match)) { + $this->error(self::ERROR_OBJECT_FOUND, $value); + + return false; + } + + return true; + } +} diff --git a/src/Validator/ObjectExists.php b/src/Validator/ObjectExists.php new file mode 100644 index 0000000..15e331f --- /dev/null +++ b/src/Validator/ObjectExists.php @@ -0,0 +1,177 @@ + + */ +class ObjectExists extends AbstractValidator +{ + /** + * Error constants + */ + const ERROR_NO_OBJECT_FOUND = 'noObjectFound'; + + /** + * @var array Message templates + */ + protected $messageTemplates = [ + self::ERROR_NO_OBJECT_FOUND => "No object matching '%value%' was found", + ]; + + /** + * ObjectRepository from which to search for entities + * + * @var ObjectRepository + */ + protected $objectRepository; + + /** + * Fields to be checked + * + * @var array + */ + protected $fields; + + /** + * Constructor + * + * @param array $options required keys are `object_repository`, which must be an instance of + * Doctrine\Common\Persistence\ObjectRepository, and `fields`, with either + * a string or an array of strings representing the fields to be matched by the validator. + * @throws Exception\InvalidArgumentException + */ + public function __construct(array $options) + { + if (! isset($options['object_repository']) || ! $options['object_repository'] instanceof ObjectRepository) { + if (! array_key_exists('object_repository', $options)) { + $provided = 'nothing'; + } else { + if (is_object($options['object_repository'])) { + $provided = get_class($options['object_repository']); + } else { + $provided = getType($options['object_repository']); + } + } + + throw new Exception\InvalidArgumentException( + sprintf( + 'Option "object_repository" is required and must be an instance of' + . ' Doctrine\Common\Persistence\ObjectRepository, %s given', + $provided + ) + ); + } + + $this->objectRepository = $options['object_repository']; + + if (! isset($options['fields'])) { + throw new Exception\InvalidArgumentException( + 'Key `fields` must be provided and be a field or a list of fields to be used when searching for' + . ' existing instances' + ); + } + + $this->fields = $options['fields']; + $this->validateFields(); + + parent::__construct($options); + } + + /** + * Filters and validates the fields passed to the constructor + * + * @throws Exception\InvalidArgumentException + */ + private function validateFields() + { + $fields = (array) $this->fields; + + if (empty($fields)) { + throw new Exception\InvalidArgumentException('Provided fields list was empty!'); + } + + foreach ($fields as $key => $field) { + if (! is_string($field)) { + throw new Exception\InvalidArgumentException( + sprintf('Provided fields must be strings, %s provided for key %s', gettype($field), $key) + ); + } + } + + $this->fields = array_values($fields); + } + + /** + * @param string|array $value a field value or an array of field values if more fields have been configured to be + * matched + * @return array + * @throws Exception\RuntimeException + */ + protected function cleanSearchValue($value): array + { + $value = is_object($value) ? [$value] : (array) $value; + + if (ArrayUtils::isHashTable($value)) { + $matchedFieldsValues = []; + + foreach ($this->fields as $field) { + if (! array_key_exists($field, $value)) { + throw new Exception\RuntimeException( + sprintf( + 'Field "%s" was not provided, but was expected since the configured field lists needs' + . ' it for validation', + $field + ) + ); + } + + $matchedFieldsValues[$field] = $value[$field]; + } + } else { + $matchedFieldsValues = @array_combine($this->fields, $value); + + /** @phan-suppress-next-line PhanTypeComparisonToArray */ + if (false === $matchedFieldsValues) { // + throw new Exception\RuntimeException( + sprintf( + 'Provided values count is %s, while expected number of fields to be matched is %s', + count($value), + count($this->fields) + ) + ); + } + } + + return $matchedFieldsValues; + } + + /** + * {@inheritDoc} + */ + public function isValid($value): bool + { + $cleanedValue = $this->cleanSearchValue($value); + $match = $this->objectRepository->findOneBy($cleanedValue); + + if (is_object($match)) { + return true; + } + + $this->error(self::ERROR_NO_OBJECT_FOUND, $value); + + return false; + } +} diff --git a/src/Validator/Service/AbstractValidatorFactory.php b/src/Validator/Service/AbstractValidatorFactory.php new file mode 100644 index 0000000..1f2a624 --- /dev/null +++ b/src/Validator/Service/AbstractValidatorFactory.php @@ -0,0 +1,112 @@ + + * @phan-file-suppress PhanTypeInvalidThrowsIsInterface + */ +abstract class AbstractValidatorFactory implements FactoryInterface +{ + const DEFAULT_OBJECTMANAGER_KEY = 'doctrine.entity_manager.orm_default'; + + protected $creationOptions = []; + + protected $validatorClass; + + /** + * @param ContainerInterface $container + * @param array $options + * @return ObjectRepository + * @throws ServiceCreationException + */ + protected function getRepository(ContainerInterface $container, array $options): ObjectRepository + { + if (empty($options['target_class'])) { + throw new ServiceCreationException(sprintf( + "Option 'target_class' is missing when creating validator %s", + __CLASS__ + )); + } + + $objectManager = $this->getObjectManager($container, $options); + $targetClassName = $options['target_class']; + /** @var ObjectRepository $objectRepository */ + $objectRepository = $objectManager->getRepository($targetClassName); + + return $objectRepository; + } + + /** + * @param ContainerInterface $container + * @param array $options + * @return ObjectManager + */ + protected function getObjectManager(ContainerInterface $container, array $options) + { + $objectManager = ($options['object_manager']) ?? self::DEFAULT_OBJECTMANAGER_KEY; + + if (is_string($objectManager)) { + $objectManager = $container->get($objectManager); + } + + return $objectManager; + } + + /** + * @param array $options + * @return array + */ + protected function getFields(array $options): array + { + if (isset($options['fields'])) { + return (array) $options['fields']; + } + + return []; + } + + /** + * Helper to merge options array passed to `__invoke` + * together with the options array created based on the above + * helper methods. + * + * @param array $previousOptions + * @param array $newOptions + * @return array + */ + protected function merge(array $previousOptions, array $newOptions): array + { + return ArrayUtils::merge($previousOptions, $newOptions, true); + } + + /** + * @param ServiceLocatorInterface $serviceLocator + * @return object + * @throws ContainerException + */ + public function createService(ServiceLocatorInterface $serviceLocator): object + { + return $this($serviceLocator, $this->validatorClass, $this->creationOptions); + } + + public function setCreationOptions(array $options) + { + $this->creationOptions = $options; + } +} diff --git a/src/Validator/Service/Exception/ServiceCreationException.php b/src/Validator/Service/Exception/ServiceCreationException.php new file mode 100644 index 0000000..15fbc3c --- /dev/null +++ b/src/Validator/Service/Exception/ServiceCreationException.php @@ -0,0 +1,16 @@ + + */ +class ServiceCreationException extends BaseRuntimeException +{ +} diff --git a/src/Validator/Service/NoObjectExistsFactory.php b/src/Validator/Service/NoObjectExistsFactory.php new file mode 100644 index 0000000..ff93bec --- /dev/null +++ b/src/Validator/Service/NoObjectExistsFactory.php @@ -0,0 +1,30 @@ + + */ +class NoObjectExistsFactory extends AbstractValidatorFactory +{ + protected $validatorClass = NoObjectExists::class; + + public function __invoke(ContainerInterface $container, $requestedName, array $options = null): NoObjectExists + { + $repository = $this->getRepository($container, $options); + + return new NoObjectExists($this->merge($options, [ + 'object_repository' => $repository, + 'fields' => $this->getFields($options), + ])); + } +} diff --git a/src/Validator/Service/ObjectExistsFactory.php b/src/Validator/Service/ObjectExistsFactory.php new file mode 100644 index 0000000..6e7bcfd --- /dev/null +++ b/src/Validator/Service/ObjectExistsFactory.php @@ -0,0 +1,30 @@ + + */ +class ObjectExistsFactory extends AbstractValidatorFactory +{ + protected $validatorClass = ObjectExists::class; + + public function __invoke(ContainerInterface $container, $requestedName, array $options = null): ObjectExists + { + $repository = $this->getRepository($container, $options); + + return new ObjectExists($this->merge($options, [ + 'object_repository' => $repository, + 'fields' => $this->getFields($options), + ])); + } +} diff --git a/src/Validator/Service/UniqueObjectFactory.php b/src/Validator/Service/UniqueObjectFactory.php new file mode 100644 index 0000000..0b5c146 --- /dev/null +++ b/src/Validator/Service/UniqueObjectFactory.php @@ -0,0 +1,24 @@ +merge($options, [ + 'object_manager' => $this->getObjectManager($container, $options), + 'use_context' => $useContext, + 'object_repository' => $this->getRepository($container, $options), + 'fields' => $this->getFields($options), + ])); + } +} diff --git a/src/Validator/UniqueObject.php b/src/Validator/UniqueObject.php new file mode 100644 index 0000000..d8e6464 --- /dev/null +++ b/src/Validator/UniqueObject.php @@ -0,0 +1,166 @@ + + */ +class UniqueObject extends ObjectExists +{ + /** + * Error constants + */ + const ERROR_OBJECT_NOT_UNIQUE = 'objectNotUnique'; + + /** + * @var array Message templates + */ + protected $messageTemplates = [ + self::ERROR_OBJECT_NOT_UNIQUE => "There is already another object matching '%value%'", + ]; + + /** + * @var ObjectManager + */ + protected $objectManager; + + /** + * @var boolean + */ + protected $useContext; + + /*** + * Constructor + * + * @param array $options required keys are `object_repository`, which must be an instance of + * Doctrine\Common\Persistence\ObjectRepository, `object_manager`, which + * must be an instance of Doctrine\Common\Persistence\ObjectManager, + * and `fields`, with either a string or an array of strings representing + * the fields to be matched by the validator. + * @throws Exception\InvalidArgumentException + */ + public function __construct(array $options) + { + parent::__construct($options); + + if (! isset($options['object_manager']) || ! $options['object_manager'] instanceof ObjectManager) { + if (! array_key_exists('object_manager', $options)) { + $provided = 'nothing'; + } else { + if (is_object($options['object_manager'])) { + $provided = get_class($options['object_manager']); + } else { + $provided = getType($options['object_manager']); + } + } + + throw new Exception\InvalidArgumentException( + sprintf( + 'Option "object_manager" is required and must be an instance of' + . ' Doctrine\Common\Persistence\ObjectManager, %s given', + $provided + ) + ); + } + + $this->objectManager = $options['object_manager']; + $this->useContext = isset($options['use_context']) ? (boolean) $options['use_context'] : false; + } + + /** + * Returns false if there is another object with the same field values but other identifiers. + * + * @param mixed $value + * @param array $context + * @return boolean + */ + public function isValid($value, $context = null): bool + { + if (! $this->useContext) { + $context = (array) $value; + } + + $cleanedValue = $this->cleanSearchValue($value); + $match = $this->objectRepository->findOneBy($cleanedValue); + + if (! is_object($match)) { + return true; + } + + $expectedIdentifiers = $this->getExpectedIdentifiers($context); + $foundIdentifiers = $this->getFoundIdentifiers($match); + + if (count(array_diff_assoc($expectedIdentifiers, $foundIdentifiers)) == 0) { + return true; + } + + $this->error(self::ERROR_OBJECT_NOT_UNIQUE, $value); + return false; + } + + /** + * Gets the identifiers from the matched object. + * + * @param object $match + * @return array + * @throws Exception\RuntimeException + */ + protected function getFoundIdentifiers($match) + { + return $this->objectManager + ->getClassMetadata($this->objectRepository->getClassName()) + ->getIdentifierValues($match); + } + + /** + * Gets the identifiers from the context. + * + * @param array|object $context + * @return array + * @throws Exception\RuntimeException + */ + protected function getExpectedIdentifiers($context = null) + { + if ($context === null) { + throw new Exception\RuntimeException( + 'Expected context to be an array but is null' + ); + } + + $className = $this->objectRepository->getClassName(); + + if ($context instanceof $className) { + return $this->objectManager + ->getClassMetadata($this->objectRepository->getClassName()) + ->getIdentifierValues($context); + } + + $result = []; + foreach ($this->getIdentifiers() as $identifierField) { + if (! array_key_exists($identifierField, $context)) { + throw new Exception\RuntimeException(\sprintf('Expected context to contain %s', $identifierField)); + } + + $result[$identifierField] = $context[$identifierField]; + } + return $result; + } + + + /** + * @return array the names of the identifiers + */ + protected function getIdentifiers() + { + return $this->objectManager + ->getClassMetadata($this->objectRepository->getClassName()) + ->getIdentifierFieldNames(); + } +}