{"id":16409573,"url":"https://github.com/pointybeard/symphony-classmapper","last_synced_at":"2025-10-26T17:32:42.918Z","repository":{"id":57043230,"uuid":"61338411","full_name":"pointybeard/symphony-classmapper","owner":"pointybeard","description":"Maps sections into custom objects, simplifying the process of creating, modifying, deleting and fetching entries in Symphony.","archived":false,"fork":false,"pushed_at":"2021-10-27T00:51:05.000Z","size":115,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-01-31T22:11:27.516Z","etag":null,"topics":["composer-package","library","symphony-cms","symphony-cms-extension"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pointybeard.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-06-17T01:59:23.000Z","updated_at":"2021-10-27T00:50:29.000Z","dependencies_parsed_at":"2022-08-23T23:40:16.202Z","dependency_job_id":null,"html_url":"https://github.com/pointybeard/symphony-classmapper","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointybeard%2Fsymphony-classmapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointybeard%2Fsymphony-classmapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointybeard%2Fsymphony-classmapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointybeard%2Fsymphony-classmapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pointybeard","download_url":"https://codeload.github.com/pointybeard/symphony-classmapper/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238380590,"owners_count":19462398,"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":["composer-package","library","symphony-cms","symphony-cms-extension"],"created_at":"2024-10-11T06:20:27.812Z","updated_at":"2025-10-26T17:32:37.664Z","avatar_url":"https://github.com/pointybeard.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Symphony CMS: Section Class Mapper\n\n- Version: 2.0.0\n- Date: June 11 2019\n- [Release notes](https://github.com/pointybeard/symphony-classmapper/blob/master/CHANGELOG.md)\n- [GitHub repository](https://github.com/pointybeard/symphony-classmapper)\n\n[![Latest Stable Version](https://poser.pugx.org/pointybeard/symphony-classmapper/version)](https://packagist.org/packages/pointybeard/symphony-classmapper) [![License](https://poser.pugx.org/pointybeard/symphony-classmapper/license)](https://packagist.org/packages/pointybeard/symphony-classmapper)\n\nMaps sections into custom model classes, simplifying the process of creating, modifying, deleting and fetching entries in Symphony CMS.\n\n## Requirements\n\nThis library requires PHP 7.2 or later. For use with earlier version of PHP, use 1.0.x instead (`composer require pointybeard/symphony-classmapper:\\\u003c2.0`).\n\n## Installation\n\nSymphony Class Mapper is installed via [Composer](http://getcomposer.org/). To install, use `composer require pointybeard/symphony-classmapper` or add `\"pointybeard/symphony-classmapper\": \"~2.0\"` to your `composer.json` file.\n\n## Usage\n\nThe most basic usage is to let Section Class Mapper create an annonomous class for you and map it on to your section by using `Classmapper\\create()`. E.g. assuming you have a section called 'articles' and a field called 'title':\n\n```php\ndeclare(strict_types=1);\n\ninclude 'vendor/autoload.php';\n\nuse pointybeard\\Symphony\\Classmapper;\n\nClassmapper\\create(\n    'Article', // Name of class to be created\n    'articles' // Handle of section to map\n);\n```\n\nNote that the second argument for `create()`, section handle, is optional. If ommitted, Classmapper will attempt to deduce your section handle from your class name (in this case, 'Article'). It does this by assuming that your section is a pluralised version of the class name.\n\nIn the above example, the class name of 'Article' would be used to deduce a corresponding section handle of `articles`. Should the class mapper not be able to locate a section, a `ClassmapperException` will be thrown.\n\nSetting a section handle is useful if your section name doesn't stick to the pluralisation assumption or if it might return an ambiguous result (i.e, more than one matching section).\n\nOnce created, Articles can be created by instanciating the newly created `Article` class, setting field values, and calling `save()`.\n\n```php\n// Create a new article\n$article = new Article;\n$article-\u003etitle('My Article');\n$article-\u003esave();\n\n// Classmapper also supports method chaining like so\n(new Article)\n    -\u003etitle('My New Article')\n    -\u003esave()\n;\n```\n\nExisting articles can be accessed using two built-in methods: `all()` and `loadFromId()`.\n\n```php\n// Get article with id of 1\n$article = Article::loadFromId(1);\n\n// Iterate over all articles\nforeach(Article::all() as $article) {\n    printf(\"%d: %s\\r\\n\", $article-\u003eid, $article-\u003etitle);\n}\n```\n\nOther useful methods include `hasBeenModified()`, `toXml()`, and `delete()`:\n\n```php\n# Check if it was modified\n$article-\u003ehasBeenModified();\n\n# Get the XML representation of your Article\n$article-\u003etoXml();\n\n# Remove the article\n$article-\u003edelete()\n\n# Alternatively, you can delete entries like this\nArticle::loadFromId(3)-\u003edelete();\n\n```\n\n### Creating Custom Model Classes\n\nThe auto-generated class produced by calling `Classmapper::create()` are useful but somewhat limited. The biggest limitation being that they cannot have custom field mappings to accomodate non-standard fields. They are useful for basic sections without complex relationships to other sections. To get around this limitation, we need reate your a concrete class. This gives you all the same built-in methods, but, allows you to expand it's API and, most importantly, define fields.\n\nTo create a custom Classmapper model, extend `AbstractModel` and use the `HasModelTrait` trait. E.g. Using the same Articles example:\n\n```php\n    \u003c?php\n\n    namespace Your\\Project\\Namespace;\n\n    use pointybeard\\Symphony\\Classmapper;\n\n    final class Article extends Classmapper\\AbstractModel\n    {\n        use Classmapper\\Traits\\HasModelTrait;\n    }\n```\n\nThe trait `HasModelTrait` provides three static member variables: `$sectionFields`, `$fieldMapping` and `$section`. They are used internally and hold a mapping to the Symphony section and the fields from that section; all are auto-populated by the parent object at run-time.\n\nIf the section has a non-standard handle, it can set manually by overloading `AbstractModel::getSectionHandle()` to return the section handle. e.g.\n\n```php\n...\npublic function getSectionHandle(): string\n{\n    return 'articles';\n}\n...\n```\n\nAt this stage, the custom Article class is identical to that produced by `Classmapper::create('Article')`, however, we have a framework for adding additional features, logic, and defining fields.\n\n### Accessing Values\n\nThe class mapper takes all the fields in a section and creates class member names for them automatically. These names are generated using the field handle and converting them to `camelCase`. E.g. \"published-date\" becomes `publishedDate` and \"my-awesome-field\" is `myAwesomeField`.\n\n### Creating A Custom Field Mapping\n\nThe class mapper assumes all fields have a `value` field in the database and that value is always a string, however, this is not true for every field. For example, a Select Box Link field has a field called `relation_id` which is an integer. In this situation you must tell the class mapper how the field should be mapped and its type. This is done by overloading the `AbstractModel::getCustomFieldMapping()` method.\n\nUsing the Article example from above, lets assume there is now a field called \"Author\" which is a Select Box Link field pointing to the \"Authors\" section. We'll tell the Class Mapper that the Author field is an integer and has a database field `relation_id` (instead of the default `value`). Finally, we'll remap the field name to be `authorId` instead of `author`.\n\n```php\n...\n# Create a mapping for the Author field, mapping the id to 'authorId'\nprotected static function getCustomFieldMapping() {\n    return [\n        'author' =\u003e [\n            'databaseFieldName' =\u003e 'relation_id',\n            'classMemberName' =\u003e 'authorId',\n            'flags' =\u003e self::FLAG_INT\n        ],\n    ];\n}\n\n# Create a method that allows easy retrieval of an Author object.\n# Note, this assumes an Author class model exists.\npublic function author() {\n    return Author::fetchFromId($this-\u003eauthorId);\n}\n...\n```\n\nYou can see how we quickly wired up the Articles model to know about Authors and how to retrieve them.\n\n### Using Flags\n\nYou can specify a `flag` property for custom field mappings (briefly covered in 'Creating A Custom Field Mapping' above) to trigger different behaviours when retrieving and saving data.\n\nFlags can be combined using the bitwise OR (`|`) operator. Note, some flags cannot, or don't make sense to, combine with other flags.\n\nHere is an example showing a more fully fleshed out Model's custom field mapping:\n\n```php\n...\nprotected static function getCustomFieldMapping() {\n    return [\n        'related-entries' =\u003e [\n            'databaseFieldName' =\u003e 'relation_id',\n            'classMemberName' =\u003e 'relatedEntryIDs',\n            'flags' =\u003e self::FLAG_ARRAY | self::FLAG_INT | self::FLAG_NULL\n        ],\n        'published' =\u003e [\n            'flags' =\u003e self::FLAG_BOOL\n        ],\n        'date' =\u003e [\n            'classMemberName' =\u003e 'dateCreatedAt',\n            'flags' =\u003e self::FLAG_SORTBY | self::FLAG_SORTDESC | self::FLAG_REQUIRED\n        ],\n        'title' =\u003e [\n            'flags' =\u003e self::FLAG_STR | self::FLAG_REQUIRED\n        ],\n        'author' =\u003e [\n            'databaseFieldName' =\u003e 'relation_id',\n            'classMemberName' =\u003e 'authorId',\n            'flags' =\u003e self::FLAG_INT | self::FLAG_REQUIRED\n        ],\n        'subtitle' =\u003e [\n            'flags' =\u003e self::FLAG_STR | self::FLAG_NULL\n        ],\n    ];\n}\n...\n```\n\n#### Type\n\nType flags signal to the Class Mapper, when data is retrieved or saved, that it should be cast\n\n*FLAG\\_INT*, *FLAG\\_STR*, and *FLAG\\_FLOAT*\n\nThese flags are used to type cast data being pulled out and map directly to the native PHP methods `intval()`, `floatval()`, and `strval()` respectivly. They can be combined with `FLAG_ARRAY`, in which case all items in the array will be cast to that type.\n\n*FLAG\\_CURRENCY*\n\nSimilar to `FLAG_FLOAT`, however, will limit the result to 2 decimal places.\n\n*FLAG\\_BOOL*\n\nConverts the data coming out of the database from `Yes|No` string value into `true|false`. When saving, it is converted back in to a Yes|No string value. Can be combined with `FLAG_ARRAY`\n\n#### Behavioural\n\n*FLAG\\_ARRAY*\n\nUse this when the field has multiple rows of data, like a multi-select. The data\nreturned will be an array of values. Can combine with `FLAG_INT`, `FLAG_STR`, `FLAG_FLOAT`, `FLAG_BOOL`, `FLAG_CURRENCY`, and `FLAG_NULL`\n\n*FLAG\\_FILE*\n\nWill set the field value to an array containing `file`, `size`, `mimetype`, and `meta`. Note, when saving only `file` is used since the other fields can be re-built by examining the file if needed. Can only combine with `FLAG_NULL`\n\n*FLAG\\_NULL*\n\nConverts empty values, i.e. int(0), string(\"\"), (array)[] etc, into `NULL`. Can be combined with all other flags. When the Class mapper build data for the model, if the field's value is empty, it will instead set it to NULL.\n\n#### Sorting\n\nSorting flags are used when retrieving data. The Class Mapper will look for these flags when building the SQL used to pull put data from the database.\n\nTo enable sorting for your model, be sure to implement `SortableModelInterface` and also use the `Traits\\HasSortableModelTrait` trait. E.g.\n\n```php\nuse pointybeard\\Symphony\\Classmapper;\n\nclass Articles extends Classmapper\\AbstractModel implements Classmapper\\Interfaces\\SortableModelInterface {\n    use Classmapper\\Traits\\HasSortableModelTrait;\n    ...\n}\n```\n\n*FLAG\\_SORTBY*\n\nWhen set, the result set will be sorted by this field. Note, the specific table column used to sort is either `value`, e.g. [field].value or `databaseFieldName` if it is set.\n\n*FLAG\\_SORTDESC* and *FLAG\\_SORTASC*\n\nDenotes the sorting direction; ASC or DESC. Cannot combine both `FLAG_SORTASC` and `FLAG_SORTDESC`. Default is `FLAG_SORTASC`.\n\n#### Validation\n\nThese flags are applied when saving.\n\n*FLAG\\_REQUIRED*\n\nSignifies that this field must have a non-empty value, otherwise saving will fail. Note that `FLAG_NULL` is NOT the opposite of `FLAG_REQUIRED`. It is possible that a field might have a null value, however, `FLAG_REQUIRED` would ensure it has a value before allowing you to save.\n\n### Validation when Saving\n\nWhen saving an entry, you can tell the Class Mapper how strict you would like it to be. e.g. `$articles-\u003esave(self::FLAG_ON_SAVE_ENFORCE_MODIFIED)`. Flags can be combined using the bitwise OR (`|`) operator (as is the case with all `FLAG_*` constants).\n\nThe following flags are supported:\n\n*FLAG\\_ON\\_SAVE\\_VALIDATE*\n\nWhen saving, all fields will be validated according to any custom field `flags` mapping. Currently the only related flag is `FLAG_REQUIRED` which will ensure the field has a non-empty value. If validation fails, a `ModelValidationFailedException` exception will be thrown. This flag is enabled by default. Pass `NULL`, `0`, or another flag to prevent valdiation when saving. e.g. `$article-\u003esave(null)`\n\n*FLAG\\_ON\\_SAVE\\_ENFORCE\\_MODIFIED*\n\nThis will trigger a `ModelHasNotBeenModifiedException` exception if you attempt to save an entry that has not been modified. Check `hasBeenModified()`\n\n### Providing Custom SQL when fetching\n\nIt might be necessary to provide custom SQL to use when the class mapper loads an object. To do this, overload the `AbstractModel::fetchSQL()` method. It should return an SQL string. You can use `self::$sectionFields` to easily access the ID values of fields in your section. E.g.\n\n```php\n...\nprotected static function fetchSQL($where = 1)\n{\n    return sprintf('\n        SELECT SQL_CALC_FOUND_ROWS\n            t.entry_id as `id`,\n            t.value as `title`,\n            f.file as `file`,\n            a.value as `available`\n        FROM `tbl_entries_data_%d` AS `t`\n        INNER JOIN `tbl_entries_data_%d` AS `f` ON f.entry_id = t.entry_id\n        LEFT JOIN `tbl_entries_data_%d` AS `a` ON a.entry_id = f.entry_id\n        WHERE %s\n        ORDER BY t.entry_id ASC',\n        self::$sectionFields['title'],\n        self::$sectionFields['file'],\n        self::$sectionFields['available'],\n        $where\n    );\n}\n...\n```\n\nNote that overloading the `fetchSQL()` method will mean you need to handle using the correct field mappings, filtering and sorting rather than letting `AbstractModel` handle it for you.\n\n### Modifying data before saving\n\nThere are times when you might need to change data on the fly before it is saved into the entry. You can do this by overloading the `AbstractModel::getData()` method. For example, you might have a \"modified date\" field in your section. By overloading the `getData` method, you can ensure it is updated automatically.\n\n```php\n...\nprotected function getData()\n{\n    $data = parent::getData();\n\n    // Check if anything has changed and, if so, set the new modified date\n    if($this-\u003ehasBeenModified()) {\n      $data['modified'] = 'now';\n    }\n\n    return $data;\n}\n...\n```\n\n### Filtering Results\n\nThe class mapper gives you the `fetchById()`, and `all()` methods out of the box. However, you'll quickly need a more powerful way of filtering down results. This is where using Filter classes come in to play.\n\nTo enable Filtering of results on your model, implement the `FilterableModelInterface` interface and use the `HasFilterableModelTrait` trait. e.g.\n\n```php\nuse pointybeard\\Symphony\\Classmapper;\n\nclass Articles extends Classmapper\\AbstractModel implements Classmapper\\Interfaces\\FilterableModelInterface {\n    use Classmapper\\Traits\\HasFilterableModelTrait;\n    ...\n}\n```\n\nThis will give you access to 5 new methods: `fetch()`, `filter()`, `appendFilter()`, `clearFilters()`, and `getFilters()` as well as an outlet to use the 5 included Filter classes: `Basic`, `FindInSet`, `IsNotNull`, `IsNull`, and `Now`\n\n#### Fetch\n\nThe simplest way to filter results is to call `fetch()`. It expects to get a objects that extend `AbstractFilter` (note that calling `fetch()` without any filters is the same as calling `all()`).\n\nFilter objects can be instanciated direct, e.g. `new Filter\\Basic(...)`, however, Class Mapper includes a factory class to make the process more consistent.\n\nHere is a simple example:\n\n```\n## Find all articles that are published and have a creation date less than now\n$article-\u003efetch(\n    Classmapper\\FilterFactory::build('Basic', 'published', 'Yes'),\n    Classmapper\\FilterFactory::build('Now', 'dateCreatedAt', Classmapper\\Filters\\Basic::COMPARISON_OPERATOR_LT),\n);\n```\n\nThe result of calling `fetch()` will be a SymphonyPDO `ResultIterator` object. The results can be accessed using a `foreach` loop or the `each()` method with a custom function. e.g.\n\n```php\nArticle::fetch(...)-\u003eeach(function ($article) {\n  // do something with $article here\n});\n\nforeach(Article::fetch(...) as $article) {\n  // do something with $article here\n}\n```\n\nEach Filter has slightly different requirements for instanciation, however, they will always following this ordering (where values in square brackets may or may not be required):\n\n    FILTER, FIELD_NAME, [VALUE], [TYPE], [COMPARISON], OPERATOR\n\nYou can check each Filter class's specific requirements by looking at their constructor.\n\n*FILTER*\n\nThis is the name of the Filter class to use. Built-in Filters include `Basic`, `FindInSet`, `IsNotNull`, `IsNull`, and `Now`.\n\n*FIELD\\_NAME*\n\nThis is the name of the field in the section as defined by the Class Mapper. It will either be the value specified by `classMemberName` in your field mappings, or the camelCase version of the field handle.\n\n*TYPE*\n\nThis is one of the [PDO's Predefined Constants](https://www.php.net/manual/en/pdo.constants.php). The default is `PDO::PARAM_STR`.\n\n*COMPARISON*\n\nThis is the comparison operator used when comparing `value` to the value in `fieldName`. These are provided by `Filter\\Basic`:\n\n```php\nCOMPARISON_OPERATOR_EQ          // '='\nCOMPARISON_OPERATOR_NEQ         // '!='\nCOMPARISON_OPERATOR_GT          // '\u003e'\nCOMPARISON_OPERATOR_GTEQ        // '\u003e='\nCOMPARISON_OPERATOR_LT          // '\u003c'\nCOMPARISON_OPERATOR_LTEQ        // '\u003c='\nCOMPARISON_OPERATOR_LIKE        // 'LIKE'\nCOMPARISON_OPERATOR_NOT_LIKE    // 'NOT LIKE'\n```\n\n*OPERATOR*\n\nThis tells the Class Mapper how to join the filters. This operator is applied between the current filter and the previous filter. Available options are `OPERATOR_OR`, and `OPERATOR_AND`. The default is `OPERATOR_AND`.\n\n\n#### Filter Classes\n\nThe 5 built-in Filters are `Basic`, `FindInSet`, `IsNotNull`, `IsNull`, and `Now`\n\n*Filters\\Basic*\n\nThis is useful for simple `a COMPARED TO b` type comparisons. It provides the operators `=`, `!=`, `\u003e`, `\u003e=`, `\u003c`, `\u003c=`, `LIKE`, and `NOT LIKE` which are available as class constants (see *COMPARISON* above).\n\nBasic expects up to 5 arguments when instanciated\n\n```php\npublic function __construct(\n    string $field,\n    $value,\n    int $type = \\PDO::PARAM_STR,\n    string $comparisonOperator = self::COMPARISON_OPERATOR_EQ,\n    string $operator = self::OPERATOR_AND\n)\n```\n\n*Filters\\FindInSet*\n\nThis filter expects to get an array of values. It will check if the field value is the same as any of the values provided.\n\nBasic expects up to 3 arguments when instanciated\n\n```php\npublic function __construct(\n    $field,\n    array $values,\n    string $operator = self::OPERATOR_AND\n)\n```\n\n*Filters\\IsNull* and *Filters\\IsNotNull*\n\nThese filters will check if a value is or is not null.\n\nBasic expects 2 arguments when instanciated.\n\n```php\npublic function __construct(\n    $field,\n    string $operator = self::OPERATOR_AND\n)\n```\n\n*Filters\\Now*\n\nThis filter extends `Filters\\Basic`, giving access to the `NOW()` feature of SQL.\n\nBasic expects up to 3 arguments when instanciated.\n\n```php\npublic function __construct(\n    string $field,\n    string $comparisonOperator = self::COMPARISON_OPERATOR_EQ,\n    string $operator = self::OPERATOR_AND\n)\n```\n\n#### Using `filter()`\n\nInstead of calling `fetch()` and providing Filters on the fly, you can create an instance of the model class and then append filters with `appendFilter()`. Once you have built up the set of filters you want, call `filter()` to return a result set. For example:\n\n```php\n## Create an instance of the Article model\n$article = new Article;\n\n## Append filters with appendFilter(). Note method chaining is supported\n$article\n    -\u003eappendFilter(Classmapper\\FilterFactory::build('Basic', 'published', 'Yes'))\n    -\u003eappendFilter(Classmapper\\FilterFactory::build('Now', 'dateCreatedAt', Classmapper\\Filters\\Basic::COMPARISON_OPERATOR_LT))\n;\n\n## Returns the results\n$result = $article-\u003efilter();\n\n## Optionally clear the filters from this instance\n$article-\u003eclearFilters();\n```\n\nThe main benefit of using `appendFilter()` and `filter()` is that you can pass the model around, allowing other sections of code to add/remove filters before finally calling `filter()`. Additionally, the result is cached in that instance so you can call `filter()` multiple times without any performance hit.\n\nTo get a result with different filters, either call `clearFilters()` or create a new instance of your model.\n\n## Support\n\nIf you believe you have found a bug, please report it using the [GitHub issue tracker](https://github.com/pointybeard/symphony-classmapper/issues),\nor better yet, fork the library and submit a pull request.\n\n## Contributing\n\nWe encourage you to contribute to this project. Please check out the [Contributing documentation](https://github.com/pointybeard/symphony-classmapper/blob/master/CONTRIBUTING.md) for guidelines about how to get involved.\n\n## License\n\n\"Symphony CMS: Section Class Mapper\" is released under the [MIT License](http://www.opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpointybeard%2Fsymphony-classmapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpointybeard%2Fsymphony-classmapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpointybeard%2Fsymphony-classmapper/lists"}