{"id":23501220,"url":"https://gitlab.com/abivia/configurable","last_synced_at":"2025-04-15T19:41:48.522Z","repository":{"id":56939474,"uuid":"11769764","full_name":"abivia/configurable","owner":"abivia","description":"Easily construct, hydrate/populate, and validate complex nested class structures from simple configuration files. Configurable is a PHP trait that transforms nested stdClass objects (typically from JSON or YAML files) into application models.\r\n","archived":false,"fork":false,"pushed_at":null,"size":null,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":null,"default_branch":"master","last_synced_at":"2025-03-29T00:23:12.841Z","etag":null,"topics":["configuration","hydration","json","php","population","yaml"],"latest_commit_sha":null,"homepage":null,"language":null,"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://gitlab.com/uploads/-/system/project/avatar/11769764/gitlab-avatar.jpg","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2019-04-09T19:51:32.713Z","updated_at":"2021-10-10T02:01:41.556Z","dependencies_parsed_at":"2022-08-21T06:50:28.255Z","dependency_job_id":null,"html_url":"https://gitlab.com/abivia/configurable","commit_stats":null,"previous_names":[],"tags_count":21,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/repositories/abivia%2Fconfigurable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/repositories/abivia%2Fconfigurable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/repositories/abivia%2Fconfigurable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/repositories/abivia%2Fconfigurable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/owners/abivia","download_url":"https://gitlab.com/abivia/configurable/-/archive/master/configurable-master.zip","host":{"name":"gitlab.com","url":"https://gitlab.com","kind":"gitlab","repositories_count":4518687,"owners_count":6936,"icon_url":"https://github.com/gitlab.png","version":null,"created_at":"2022-05-30T11:31:42.605Z","updated_at":"2024-07-18T11:24:13.055Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/owners"}},"keywords":["configuration","hydration","json","php","population","yaml"],"created_at":"2024-12-25T07:13:41.278Z","updated_at":"2025-04-15T19:41:48.487Z","avatar_url":"https://gitlab.com/uploads/-/system/project/avatar/11769764/gitlab-avatar.jpg","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"Abivia\\Configurable\n====\n\nFor a more robust, easier to use, and better documented solution please see \n[abivia\\hydration](https://gitlab.com/abivia/hydration/). Configurable will be maintained for\nthe foreseeable future but no new development work is being done. \n\nConfigurable is designed to make it easy to hydrate (populate) complex data structures from user editable JSON or YAML\nsources. If your application\n\n- has configurations with several levels of nesting,\n- isn't validating user editable data in configuration files,\n- is spending a lot of effort reading from the stdClass objects created by `json_decode()` or `yaml_parse()` to convert\n  them into your application's class structures, or\n- is just using poorly typed, IDE unfriendly `stdClass` objects for\nconfiguration\n\nthen Configurable is here to help.\n\nConfigurable makes it easy to convert of a set of untyped data structures\nreturned by decoding JSON or YAML configuration files, into PHP classes.\nConfigurable can\n\n- create associative arrays of named classes indexed by a property of the\nclass,\n- selectively cast properties to an array,\n- validate the source data,\n- guard against overwriting of protected properties, and\n- map properties from user-friendly names to application-meaningful identifiers.\n\nExample\n----\n`Configurable` makes it easy to take data like this:\n\n```json\n{\n    \"application-name\": \"MyApp\",\n    \"database\": [\n        {\n            \"label\": \"crm\",\n            \"driver\": \"mysql\",\n            \"host\": \"localhost\",\n            \"name\": \"crm\",\n            \"pass\": \"insecure\",\n            \"port\": 3306,\n            \"user\": \"admin\"\n        },\n        {\n            \"label\": \"geocoder\",\n            \"driver\": \"mysql\",\n            \"host\": \"localhost\",\n            \"name\": \"geo\",\n            \"pass\": \"insecure\",\n            \"port\": 3306,\n            \"user\": \"admin\"\n        }\n    ]\n}\n```\n\nand turn it into a class structure like this:\n\n```\nEnvironment Object\n(\n    [appName] =\u003e MyApp\n    [database] =\u003e Array\n        (\n            [crm] =\u003e DatabaseConfiguration Object\n                (\n                    [driver:protected] =\u003e mysql\n                    [host:protected] =\u003e localhost\n                    [key] =\u003e crm\n                    [label:protected] =\u003e crm\n                    [name:protected] =\u003e crm\n                    [pass:DatabaseConfiguration:private] =\u003e insecure\n                    [port:protected] =\u003e 3306\n                    [user:protected] =\u003e admin\n                )\n\n            [geocoder] =\u003e DatabaseConfiguration Object\n                (\n                    [driver:protected] =\u003e mysql\n                    [host:protected] =\u003e localhost\n                    [key] =\u003e geocoder\n                    [label:protected] =\u003e geocoder\n                    [name:protected] =\u003e geo\n                    [pass:DatabaseConfiguration:private] =\u003e insecure\n                    [port:protected] =\u003e 3306\n                    [user:protected] =\u003e admin\n                )\n        )\n)\n```\n\nFeatures\n----\n\n`Configurable` supports property mapping, gated properties (via allow, block, and ignore methods),\ndata validation, and data-driven class instantiation. It will map arrays of objects to\nassociative arrays of PHP classes that are indexed by any unique scalar property in the object.\n\nLoading can be either fault-tolerant or strict. Strict validation can either fail with a\n`false` result or by throwing an Exception you specify.\n\nInstallation\n----\n\n```composer require abivia/configurable```\n\nConfigurable uses the Symphony YAML parser.\n\n\nBasic Use\n----\n\n- Implement `configureClassMap()` for the top level class and any properties that map to your classes.\n- Add the configurable trait to your classes.\n- Instantiate the top level class and pass your decoded JSON or YAML to the `configure()` method.\n\nBasic example:\n```php\nclass ConfigurableObject {\n    use \\Abivia\\Configurable\\Configurable;\n\n    public $userName;\n    public $password;\n\n}\n$json = '{\"userName\": \"admin\"; \"password\": \"insecure\"}';\n$obj = new ConfigurableObject();\n$obj-\u003econfigure(json_decode($json));\necho $obj-\u003euserName . ', ' . $obj-\u003epassword;\n```\nOutput:\nadmin, insecure\n\nSelectively Convert Objects to Arrays\n---\n\nThe `json_decode()` method has an option to force conversion of objects\nto arrays, but there is no way to get selective conversion. Configurable can\ndo this via a class map to 'array'. See `CastArrayTest.php`\nfor a working example.\n\n```php\nclass ConfigCastArray\n{\n    use \\Abivia\\Configurable\\Configurable;\n\n    /**\n     * @var array\n     */\n    public $simple = [];\n\n    protected function configureClassMap($property, $value)\n    {\n        if ($property === 'simple') {\n            return ['className' =\u003e 'array'];\n        }\n        return false;\n    }\n}\n$json = '{\"simple\": { \"a\": \"this is a\", \"*\": \"this is *\"}}';\n\n$hydrate = new ConfigCastArray();\n$hydrate-\u003econfigure(json_decode($json));\nprint_r($hydrate);\n```\nWill give this result\n\n```\n(\n    [simple] =\u003e Array\n        (\n            [a] =\u003e this is a\n            [*] =\u003e this is *\n        )\n)```\n\n\nAssociative arrays using a property as the key\n---\n\nHave an array of objects with a property that you'd like to extract for use as\nan array key? No problem.\n\n```php\nclass SocialMedia\n{\n    public array $list;\n\n    protected function configureClassMap($property, $value)\n    {\n        if ($property === 'list') {\n            return ['className' =\u003e 'stdClass', 'key' =\u003e 'code'];\n        }\n        return false;\n    }\n}\n\n$json = '{\"list\":[\n    {\"code\": \"fb\", \"name\": \"Facebook\"},\n    {\"code\": \"t\", \"name\": \"Twitter\"},\n    {\"code\": \"ig\", \"name\": \"Instagram\"}\n]}\n';\n\n$hydrate = new SocialMedia();\n$hydrate-\u003econfigure(json_decode($json));\necho implode(', ', array_keys($hydrate-\u003elist) \"\\n\";\necho $hydrate-\u003elist['ig']-\u003ename;\n```\n\nWill output\n\n```\nfb, t, ig\nInstagram\n```\n\nAdvanced Use\n---\n\n- If you need to map properties, implement `configurePropertyMap()` where needed.\n- Add property validation by implementing `configureValidate()`.\n- Gate access to properties by implementing any of `configurePropertyAllow()`,\n  `configurePropertyBlock()` or `configurePropertyIgnore()`.\n- You can also initialize the class instance at run time with `configureInitialize()`.\n- Semantic validation of the result can be performed at the end of the loading process by\n  implementing `configureComplete()`.\n\nOptions\n---\nThe options parameter can contain these elements:\n\n- 'newLog' If missing or set true, Configurable's error log is cleared before any\n  processing.\n- 'parent' when instantiating a subclass, this is a reference to the parent class.\n- 'strict' Controls error handling. If strict is false, Configurable will ignore minor\n  issues such as additional properties. If strict is true, Configurable will return false\n  if any errors are encountered. If strict is a string, this will be taken as the name of\n  a Throwable class, and an instance of that class will be thrown.\n\nApplications can also pass in their own context via options. The current options are\navailable via the `$configureOptions` property. Option names starting with an underscore\nare guaranteed to not conflict with future options used by Configurable.\n\nNote that a copy of the options array is passed to subclass configuration, no data can\nbe returned to the parent via this array.\n\nFiltering\n-----\nThe properties of the configured object can be explicitly permitted by overriding the\n`configurePropertyAllow()` method, blocked by overriding the `configurePropertyBlock()`\nmethod, or ignored via the `configurePropertyIgnore()` method. Ignore takes precedence, then\nblocking, then allow. By default, attempts to set guarded properties\nare ignored, but if the $strict parameter is either true or the name of a `Throwable`\nclass, then the configuration will terminate when the invalid parameter is encountered,\nunless it has been explicitly ignored.\n\nFor a JSON input like this\n```json\n{\n    \"depth\": 15,\n    \"length\": 22,\n    \"primary\": \"Red\",\n    \"width\": 3\n}\n```\n\nwith a class that does not have the `primary` property, the result depends on the\n`strict` option:\n```php\n    class SomeClass {\n        use \\Abivia\\Configurable;\n\n        protected $depth;\n        protected $length;\n        protected $width;\n\n    }\n    $jsonDecoded = json_decode('some valid json string');\n    $obj = new SomeClass();\n    // Returns true\n    $obj-\u003econfigure($jsonDecoded);\n    // Lazy validation: Returns true\n    $obj-\u003econfigure($jsonDecoded, ['strict' =\u003e false]);\n    // Strict validation: Returns false\n    $obj-\u003econfigure($jsonDecoded, ['strict' =\u003e true]);\n    // Strict validation: throws MyException\n    $obj-\u003econfigure($jsonDecoded, ['strict' =\u003e 'MyException']);\n ```\n\nInitialization and Completion\n---\nIn many cases it is required that the object be in a known state before configuration,\nand that the configured object has acceptable values. `Configurable` supplies\n`configureInitialize()` and `configureComplete()` for this purpose. `configureInitialize()`\ncan be used to return a previously instantiated object to a known state.\n`configureInitialize()` gets passed references to the configuration data and the options\narray, and is thus able to pre-process the inputs if required.\n\nOne use case for pre-processing during initialization is to allow shorthand expressions.\nFor example, if you have an object with one property:\n```json\n{\"name\": \"foo\"}\n```\nYour application can support a shorthand expression:\n```json\n\"somevalue\"\n```\nWith this code in the initialization:\n```php\nclass MyClass\n{\n    protected function configureInitialize(\u0026$config) {\n        if (is_string($config)) {\n            $obj = new stdClass;\n            $obj-\u003ename = $config;\n            $config = $obj;\n        }\n    }\n}\n```\n\n\nValidation\n---\nScalar properties can be validated with `configureValidate()`. This method takes the property name and the input value\nas arguments. The value is passed by reference so that the validation can enforce specific formats required by the\nobject (for example by forcing case or cleaning out unwanted characters).\n\nThe `configureComplete()` method provides a mechanism for object level validation. For example, this method could be\nused to validate a set of access credentials, logging an error or aborting the configuration process entirely if they\nare not valid.\n\nProperty Name Mapping\n---\nSince configuration files allow properties that are not valid PHP property names,\n`configurePropertyMap()` can be used to convert illegal input properties to valid PHP identifiers.\n\n```php\nclass MyClass\n{\n    protected function configurePropertyMap($property) {\n        if ($property[0] == '$') {\n            $property = 'dollar_' . substr($property, 1);\n        }\n        return $property;\n    }\n}\n```\n\nIf the property does not reference another configurable class then\nthe method can also return an array containing a property name and array index.\nFor example this json:\n\n```json\n{\n    \"prop.one\": \"element one\",\n    \"prop.six\": \"element six\"\n}\n```\n\nCan be used to create an array:\n\n````php\n$configured-\u003eprop = ['one' =\u003e 'element one', 'six' =\u003e 'element six'];\n````\n\nContained Classes\n---\nThe real power of Configurable is through `configureClassMap()` which can be\nused to instantiate and configure classes that are contained in the current\nclass. Contained classes must either be `stdClass` or provide the\n `configure()` method, either via the `Configurable` trait or by providing\ntheir own method.\n\n`configureClassMap()` takes the name and value of a property as arguments and returns:\n\n- the name of a class to be instantiated and configured, or\n- an array or object that has the `className` property and any of the optional properties.\n\n### className (string|callable)\n`className` can be the name of a class that will be instantiated and configured,\nor it can be a callable that takes the current property value as an argument.\nThis allows the creation of data-specific object classes.\n\nIf a property `foo` returns an array of `['className' =\u003e 'MyClass']` or just\nthe string `MyClass` then Configurable will instantiate a new `MyClass` and\npass the value to the `configure()` method.\n\n### construct (bool)\n`className` is the name of a class that will be instantiated by passing the\n value to the class constructor.\n\nIf a property `foo` returns an array of `['className' =\u003e 'DateInterval', 'construct' =\u003e true]`\nthen Configurable will create a `new DateInterval($value)` and assign it to `foo`.\n\n### constructUnpack (bool)\n`className` is the name of a class that will be instantiated by passing the\n unpacked value (which must be an array) to the class constructor.\n\nIf a property `foo` returns an array of `['className' =\u003e 'MyClass', 'constructUnpack' =\u003e true]`\nthen Configurable will create a `new MyClass(...$value)` and assign it to `foo`.\n\n### key (string|callable)\nThe `key` property is optional and tells Configurable to populate an array.\n\n - if `key` is absent or blank, the constructed object is appended to the array,\n - if `key` is a string, then it is taken as the name of a property or method (if\n`keyIsMethod` is true) in the constructed object, and this value is used as the\nkey for an associative array, and\n - if `key` is a callable array, then it is called with the object under construction\nas an argument.\n\n### keyIsMethod (bool)\nThe `keyIsMethod` property is only used when `key` is present and not a callable. When\nset, `key` is treated as a method of the constructed object. Typically this is a getter.\n\n### allowDups (bool)\nIf Configurable is creating an associative array, the normal response to a duplicate\nkey is to generate an error message. if the `allowDups` flag is present and set,\nno error is generated.\n\nError Logging\n-------------\nAny problems encountered during configuration are logged. An array of errors can be\nretrieved by calling the `configureGetErrors()` method. The error log is cleared by an\napplication call to `configure()` unless the newLog option is set to false.\n\nUnit Tests and Examples\n========\nUnit tests are organized by PHP support level. Tests that use features of PHP\nthat are not available in PHP 7.2 are maintained in separate directories.\nPHPUnit will automatically run tests up to your current PHP version but\nnot above.\n\nThe unit tests also contain a number of examples that should be helpful in\nunderstanding how Configurable works. More detailed\nexamples with sample output can be found at\nhttps://gitlab.com/abivia/configurable-examples","project_url":"https://awesome.ecosyste.ms/api/v1/projects/gitlab.com%2Fabivia%2Fconfigurable","html_url":"https://awesome.ecosyste.ms/projects/gitlab.com%2Fabivia%2Fconfigurable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/gitlab.com%2Fabivia%2Fconfigurable/lists"}