{"id":22764578,"url":"https://github.com/kuria/options","last_synced_at":"2025-04-14T23:15:34.989Z","repository":{"id":57009931,"uuid":"149887836","full_name":"kuria/options","owner":"kuria","description":"Resolve structured arrays according to the specified set of options","archived":false,"fork":false,"pushed_at":"2023-04-22T13:47:11.000Z","size":45,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-14T23:15:30.407Z","etag":null,"topics":["configuration","defaults","nested","normalizer","options","php","resolver","validator"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kuria.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGELOG.rst","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-22T15:07:24.000Z","updated_at":"2024-11-18T11:24:49.000Z","dependencies_parsed_at":"2022-08-21T13:10:12.805Z","dependency_job_id":null,"html_url":"https://github.com/kuria/options","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kuria%2Foptions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kuria%2Foptions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kuria%2Foptions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kuria%2Foptions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kuria","download_url":"https://codeload.github.com/kuria/options/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248975329,"owners_count":21192210,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["configuration","defaults","nested","normalizer","options","php","resolver","validator"],"created_at":"2024-12-11T12:09:23.919Z","updated_at":"2025-04-14T23:15:34.963Z","avatar_url":"https://github.com/kuria.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"Options\n#######\n\nResolve structured arrays (e.g. configuration) according to the specified set of options.\n\n.. image:: https://travis-ci.com/kuria/options.svg?branch=master\n   :target: https://travis-ci.com/kuria/options\n\n.. contents::\n   :depth: 4\n\n\nFeatures\n********\n\n- type validation\n- typed lists\n- nullable options\n- choices\n- default values\n- lazy defaults (that may depend on other options)\n- custom validators and normalizers\n- nested options (multi-dimensional arrays)\n- custom resolver context\n\n\nRequirements\n************\n\n- PHP 7.1+\n\n\nUsage\n*****\n\nResolving options\n=================\n\nUse ``Resolver`` to resolve arrays according to the specified options.\n\nThe ``resolve()`` method returns an instance of ``Node``, which can be\naccessed as an array. See `Working with Node instances`_.\n\nIf the passed value is invalid, ``ResolverException`` will be thrown.\nSee `Handling validation errors`_.\n\n.. code:: php\n\n   \u003c?php\n\n   use Kuria\\Options\\Resolver;\n   use Kuria\\Options\\Option;\n\n   // create a resolver\n   $resolver = new Resolver();\n\n   // define options\n   $resolver-\u003eaddOption(\n       Option::string('path'),\n       Option::int('interval')-\u003edefault(null)\n   );\n\n   // resolve an array\n   $node = $resolver-\u003eresolve([\n      'path' =\u003e 'file.txt',\n   ]);\n\n   var_dump($node['path'], $node['interval']);\n\nOutput:\n\n::\n\n  string(8) \"file.txt\"\n  NULL\n\n\nWorking with ``Node`` instances\n-------------------------------\n\nBy default, ``Resolver-\u003eresolve()`` returns a ``Node`` instance with the resolved options.\n\n- ``Node`` implements ``ArrayAccess``, so the individual options can be acessed\n  using array syntax: ``$node['option']``\n\n- `lazy default values \u003cLazy default values (leaf-only)_\u003e`_ are resolved once that\n  option is read (or when ``toArray()`` is called)\n\n- nested `node options \u003cNode options_\u003e`_ are also returned as ``Node`` instances\n\n  (if you need to work exclusively with arrays, use ``$node-\u003etoArray()``)\n\n\nResolver context\n----------------\n\n``Resolver-\u003eresolve()`` accepts a second argument, which may be an array of additional arguments\nto pass to all validators, normalizers and lazy default closures. The values may be of any type.\n\n.. code:: php\n\n   use Kuria\\Options\\Node;\n   use Kuria\\Options\\Option;\n   use Kuria\\Options\\Resolver;\n\n   $resolver = new Resolver();\n\n   $resolver-\u003eaddOption(\n       Option::string('option')\n           -\u003enormalize(function (string $value, $foo, $bar) {\n               echo 'NORMALIZE: ', $foo, ', ', $bar, \"\\n\";\n\n               return $value;\n           })\n           -\u003evalidate(function (string $value, $foo, $bar) {\n               echo 'VALIDATE: ', $foo, ', ', $bar, \"\\n\";\n           }),\n       Option::string('optionWithLazyDefault')\n           -\u003edefault(function (Node $node, $foo, $bar) {\n               echo 'DEFAULT: ', $foo, ', ', $bar, \"\\n\";\n\n               return 'value';\n           })\n   );\n\n   $options = $resolver-\u003eresolve(\n       ['option' =\u003e 'value'],\n       ['context argument 1', 'context argument 2']\n   )-\u003etoArray();\n\n\n\nOutput:\n\n::\n\n  NORMALIZE: context argument 1, context argument 2\n  VALIDATE: context argument 1, context argument 2\n  DEFAULT: context argument 1, context argument 2\n\n\nDefining options\n================\n\nTerminology\n-----------\n\n.. _opt_terms:\n\nleaf option\n  An option in the option tree that does not contain children.\n\nnode option\n  An option defined via ``Option::node()`` or ``Option::nodeList()``.\n  They are branches in the option tree.\n\nchild option\n  Any option nested inside a node option. It can be either leaf or a node option.\n\n\nOption factories\n----------------\n\nThe ``Option`` class provides a number of static factories to create option instances.\n\n========================================== ===================================================\nFactory                                    Description\n========================================== ===================================================\n``Option::any($name)``                     Mixed option that accepts all value types.\n                                           ``NULL`` is accepted only if the option is nullable.\n\n``Option::bool($name)``                    Boolean option.\n\n``Option::int($name)``                     Integer option.\n\n``Option::float($name)``                   Float option.\n\n``Option::number($name)``                  Number option that accepts integers and floats.\n\n``Option::numeric($name)``                 Numeric option that accepts integers, floats\n                                           and numeric strings.\n\n``Option::string($name)``                  String option.\n\n``Option::array($name)``                   Array option. The individual values are not validated.\n\n``Option::list($name, $type)``             List option that accepts an array with values of the\n                                           specified type. Each value is validated and must not\n                                           be ``NULL``. See `Supported types`_.\n\n``Option::iterable($name)``                Iterable option that accepts both arrays and ``Traversable``\n                                           instances. The individual values are not validated.\n\n``Option::object($name)``                  Object option.\n\n``Option::object($name, $className)``      Object option that only accepts instances of the given\n                                           class or interface (or their descendants).\n\n``Option::resource($name)``                Resource option.\n\n``Option::scalar($name)``                  Scalar option that accepts integers, floats, strings\n                                           and booleans.\n\n``Option::choice($name, ...$choices)``     Choice option that accepts one of the listed values only\n                                           (compared in strict mode).\n\n``Option::choiceList($name, ...$choices)`` Choice list option that accepts an array consisting of\n                                           any of the listed values (compared in strict mode).\n                                           Duplicates are allowed. ``NULL`` values are not allowed.\n\n``Option::node($name, ...$options)``       Node option that accepts an array of the specified options.\n                                           See `Node options`_.\n\n``Option::nodeList($name, ...$options)``   Node list option that accepts an array of arrays of the\n                                           specified options. See `Node options`_.\n========================================== ===================================================\n\n\nOption configuration\n--------------------\n\nOption instances can be configured further by using the following methods.\n\nAll methods implement a fluent interface, for example:\n\n.. code:: php\n\n   \u003c?php\n\n   use Kuria\\Options\\Option;\n\n   Option::string('name')\n      -\u003edefault('foo')\n      -\u003enullable();\n\n\n``required()``\n^^^^^^^^^^^^^^\n\nMakes the option required (and removes any previously set default value).\n\n- `a leaf option \u003copt_terms_\u003e`_ is required by default\n\n- `a node option \u003copt_terms_\u003e`_ is not required by default, but having\n  a required `child option \u003copt_terms_\u003e`_ will make it required\n  (unless the node option itself defaults to ``NULL``).\n\n\n``default($default)``\n^^^^^^^^^^^^^^^^^^^^^\n\nMakes the option optional and specifies a default value.\n\n- specifying ``NULL`` as the default value also makes the option nullable\n\n- default value of `a leaf option \u003copt_terms_\u003e`_ is not subject to validation\n  or normalization and is used as-is\n\n- default value of `a node option \u003copt_terms_\u003e`_ must be an array or ``NULL``\n  and is validated and normalized according to the specified `child options \u003copt_terms_\u003e`_\n\n\nLazy default values (leaf-only)\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nTo specify a lazy default value, pass a closure with the following signature:\n\n.. code:: php\n\n   \u003c?php\n\n   use Kuria\\Options\\Node;\n   use Kuria\\Options\\Option;\n\n   Option::string('foo')-\u003edefault(function (Node $node) {\n       // return value can also depend on other options\n       return 'default';\n   });\n\nOnce the default value is needed, the closure will be called and its return\nvalue stored for later use (so it will not be called more than once).\n\n.. NOTE::\n\n   The typehinted ``Node`` parameter is required. A closure with incompatible\n   signature will be considered a default value itself and returned as-is.\n\n.. NOTE::\n\n   `Node options \u003copt_terms_\u003e`_ do not support lazy default values.\n\n.. TIP::\n\n   It is possible to pass additional arguments to all lazy default closures.\n   See `Resolver context`_.\n\n\n``nullable()``\n^^^^^^^^^^^^^^\n\nMake the option nullable, accepting ``NULL`` in addition to the specified type.\n\n\n``notNullable()``\n^^^^^^^^^^^^^^^^^\n\nMake the option non-nullable, not accepting ``NULL``.\n\n.. NOTE::\n\n   Options are non-nullable by default.\n\n\n``allowEmpty()``\n^^^^^^^^^^^^^^^^\n\nAllow empty values to be passed to this option.\n\n.. NOTE::\n\n   Options accept empty values by default.\n\n\n``notEmpty()``\n^^^^^^^^^^^^^^\n\nMake the option reject empty values.\n\nA value is considered empty if `PHP's empty() \u003chttp://php.net/manual/en/function.empty.php\u003e`_\nreturns ``TRUE``.\n\n\n``normalize(callable $normalizer)``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAppend a normalizer to the option. The normalizer should accept a value\nand return the normalized value or throw ``Kuria\\Options\\Exception\\NormalizerException``\non failure.\n\nSee `Normalizer and validator value types`_.\n\n- normalizers are called before validators defined by ``validate()``\n- normalizers are called in the order they were appended\n- normalizers are not called if the type of the value is not valid\n- the order in which options are normalized is undefined\n  (but `node options \u003copt_terms_\u003e`_ are normalized in child-first order)\n\n.. code:: php\n\n   \u003c?php\n\n   use Kuria\\Options\\Resolver;\n   use Kuria\\Options\\Option;\n\n   $resolver = new Resolver();\n\n   $resolver-\u003eaddOption(\n       Option::string('name')-\u003enormalize('trim')\n   );\n\n   var_dump($resolver-\u003eresolve(['name' =\u003e '  foo bar  ']));\n\n\nOutput:\n\n::\n\n  object(Kuria\\Options\\Node)#7 (1) {\n    [\"name\"]=\u003e\n    string(7) \"foo bar\"\n  }\n\n.. NOTE::\n\n   To normalize all options at the root level, define one or more normalizers\n   using ``$resolver-\u003eaddNormalizer()``.\n\n.. TIP::\n\n   It is possible to use normalizers to convert nodes into custom objects,\n   so you don't have to work with anonymous ``Node`` objects.\n\n.. TIP::\n\n   It is possible to pass additional arguments to all normalizers.\n   See `Resolver context`_.\n\n\n``validate(callable $validator)``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAppend a validator to the option. The validator should accept and validate a value.\n\n- validators are called after normalizers defined by ``normalize()``\n- validators are called in the order they were appended\n- validators are not called if the type of the value is not valid\n  or its normalization has failed\n- if a validator returns one or more errors, no other validators of that option\n  will be called\n- the order in which options are validated is undefined\n  (but `node options \u003copt_terms_\u003e`_ are validated in child-first order)\n\nThe validator should return one of the following:\n\n- ``NULL`` or an empty array if there no errors\n- errors as a ``string``, an array of strings or Error instances\n\n.. code:: php\n\n   \u003c?php\n\n   use Kuria\\Options\\Exception\\ResolverException;\n   use Kuria\\Options\\Resolver;\n   use Kuria\\Options\\Option;\n\n   $resolver = new Resolver();\n\n   $resolver-\u003eaddOption(\n      Option::string('email')-\u003evalidate(function (string $email) {\n          if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {\n              return 'must be a valid email address';\n          }\n      })\n   );\n\n   try {\n       var_dump($resolver-\u003eresolve(['email' =\u003e 'this is not an email']));\n   } catch (ResolverException $e) {\n       echo $e-\u003egetMessage(), \"\\n\";\n   }\n\nOutput:\n\n::\n\n  Failed to resolve options due to following errors:\n\n  1) email: must be a valid email address\n\n\n.. NOTE::\n\n   To validate all options at the root level, define one or more validators\n   using ``$resolver-\u003eaddValidator()``.\n\n.. TIP::\n\n   It is possible to pass additional arguments to all validators.\n   See `Resolver context`_.\n\n\nSupported types\n---------------\n\n- ``NULL`` - any type\n- ``\"bool\"``\n- ``\"int\"``\n- ``\"float\"``\n- ``\"number\"`` - integer or float\n- ``\"numeric\"`` - integer, float or a numeric string\n- ``\"string\"``\n- ``\"array\"``\n- ``\"iterable\"`` - array or an instance of ``Traversable``\n- ``\"object\"``\n- ``\"resource\"``\n- ``\"scalar\"`` - integer, float, string or a boolean\n- ``\"callable\"``\n\nAny other type is considered to be a class name, accepting instances of the given\nclass or interface (or their descendants).\n\nAn option defined as nullable will also accept a ``NULL`` value. See `nullable()`_.\n\n\nNormalizer and validator value types\n------------------------------------\n\nThe type of the value passed to normalizers and validators depend on the type\nof the option.\n\n- ``Option::list()``, ``Option::choiceList()`` - an array of values\n- ``Option::node()`` - a ``Node`` instance\n- ``Option::nodeList()`` - an array of ``Node`` instances\n- other - depends on the type of the option (``string``, ``int``, etc.)\n\n.. NOTE::\n\n   A normalizer may modify or replace the value (including its type) before\n   it is passed to subsequent normalizers and validators.\n\n\nNode options\n------------\n\nNode options accept an array of the specified options. With them it is possible\nto resolve more complex structures.\n\n- node options are resolved iteratively (without recursion)\n- certain configuration behaves differently with node options, see `Option configuration`_\n\n.. code:: php\n\n   \u003c?php\n\n   use Kuria\\Options\\Option;\n   use Kuria\\Options\\Resolver;\n\n   $resolver = new Resolver();\n\n   $resolver-\u003eaddOption(\n       Option::string('username'),\n       Option::node(\n           'personalInformation',\n           Option::int('birthYear'),\n           Option::int('height')-\u003edefault(null),\n           Option::float('weight')-\u003edefault(null)\n       ),\n       Option::nodeList(\n           'securityLog',\n           Option::string('action'),\n           Option::int('timestamp'),\n           Option::node(\n               'client',\n               Option::string('ip'),\n               Option::string('userAgent')\n           )\n       )\n   );\n\n\nHandling validation errors\n==========================\n\nThe ``Resolver-\u003eresolve()`` method throws ``Kuria\\Options\\Exception\\ResolverException``\non failure.\n\nThe specific errors can be retrieved by calling ``getErrors()`` on the exception object.\n\n\n.. code:: php\n\n   \u003c?php\n\n   use Kuria\\Options\\Resolver;\n   use Kuria\\Options\\Exception\\ResolverException;\n   use Kuria\\Options\\Option;\n\n   $resolver = new Resolver();\n\n   $resolver-\u003eaddOption(\n       Option::string('name'),\n       Option::int('level'),\n       Option::int('score')\n   );\n\n   try {\n       $resolver-\u003eresolve([\n           'name' =\u003e null,\n           'level' =\u003e 'not_a_string',\n           'foo' =\u003e 'bar',\n       ]);\n   } catch (ResolverException $e) {\n       foreach ($e-\u003egetErrors() as $error) {\n           echo $error-\u003egetFormattedPath(), \"\\t\", $error-\u003egetMessage(), \"\\n\";\n       }\n   }\n\nOutput:\n\n::\n\n  name    string expected, but got NULL instead\n  level   int expected, but got \"not_a_string\" instead\n  score   this option is required\n  foo     unknown option\n\n\nIgnoring unknown keys\n=====================\n\nThe ``Resolver`` can be configured to ignore unknown keys by calling\n``$resolver-\u003esetIgnoreUnknown(true)``.\n\n- ``UnknownOptionError`` will no longer be raised for unknown keys\n- this applies to nested options as well\n- the unknown keys will be present among the resolved options\n\n\nIntegrating the options resolver\n================================\n\nThe ``StaticOptionsTrait`` can be used to easily add static option support\nto a class.\n\nIt has the added benefit of caching and reusing the resolver in multiple\ninstances of the class. If needed, the cache can be cleared by calling\n``Foo::clearOptionsResolverCache()``.\n\n.. code:: php\n\n   \u003c?php\n\n   use Kuria\\Options\\Integration\\StaticOptionsTrait;\n   use Kuria\\Options\\Node;\n   use Kuria\\Options\\Option;\n   use Kuria\\Options\\Resolver;\n\n   class Foo\n   {\n       use StaticOptionsTrait;\n\n       /** @var Node */\n       private $config;\n\n       function __construct(array $options)\n       {\n           $this-\u003econfig = static::resolveOptions($options);\n       }\n\n       protected static function defineOptions(Resolver $resolver): void\n       {\n           $resolver-\u003eaddOption(\n               Option::string('path'),\n               Option::bool('enableCache')-\u003edefault(false)\n           );\n       }\n\n       function dumpConfig(): void\n       {\n           var_dump($this-\u003econfig);\n       }\n   }\n\nInstantiation example:\n\n.. code:: php\n\n   \u003c?php\n\n   $foo = new Foo(['path' =\u003e 'file.txt']);\n\n   $foo-\u003edumpConfig();\n\nOutput:\n\n::\n\n  object(Kuria\\Options\\Node)#8 (2) {\n    [\"path\"]=\u003e\n    string(8) \"file.txt\"\n    [\"enableCache\"]=\u003e\n    bool(false)\n  }\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkuria%2Foptions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkuria%2Foptions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkuria%2Foptions/lists"}