{"id":27158917,"url":"https://github.com/activecollab/databasestructure","last_synced_at":"2025-04-08T22:39:29.048Z","repository":{"id":1353254,"uuid":"42403233","full_name":"activecollab/databasestructure","owner":"activecollab","description":"Code generator from model structure, evolved","archived":false,"fork":false,"pushed_at":"2024-11-06T20:34:55.000Z","size":1337,"stargazers_count":1,"open_issues_count":1,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-30T13:03:28.372Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://labs.activecollab.com","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/activecollab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2015-09-13T16:23:07.000Z","updated_at":"2022-03-03T10:13:07.000Z","dependencies_parsed_at":"2023-12-04T09:25:01.019Z","dependency_job_id":"96c6482b-7d8f-4de3-b86d-af5de9db4cbb","html_url":"https://github.com/activecollab/databasestructure","commit_stats":{"total_commits":535,"total_committers":5,"mean_commits":107.0,"dds":"0.15514018691588782","last_synced_commit":"8840b9b880549e3ed7b0944b53603b20afc7db38"},"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/activecollab%2Fdatabasestructure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/activecollab%2Fdatabasestructure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/activecollab%2Fdatabasestructure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/activecollab%2Fdatabasestructure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/activecollab","download_url":"https://codeload.github.com/activecollab/databasestructure/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247941718,"owners_count":21022035,"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":[],"created_at":"2025-04-08T22:39:28.520Z","updated_at":"2025-04-08T22:39:29.040Z","avatar_url":"https://github.com/activecollab.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DatabaseStructure Library\n\n[![Build Status](https://travis-ci.org/activecollab/databasestructure.svg?branch=master)](https://travis-ci.org/activecollab/databasestructure)\n\n## Version 1.0 To Do\n\n- [ ] Get code coverage over 90%,\n- [ ] Add `serialize` method to fields, so they are automatically added to the serialization list when they are added to a type,\n- [ ] Prefix all base classes with `Base`,\n- [ ] Sufix all managers and collections with `Manager` and `Collection` respectively,\n- [ ] Check for possible collisions between field and attribute that are added by associations,\n- [ ] Add `release` and `clear` methods to Has Many, and Has Many Via associations, \n- [ ] Add `ChildInterface`, and make sure that `ParentField` adds it to models that include it,\n- [ ] Associations should automatically add connection fields to the list of fields to be serialized,\n- [ ] Association cascading options and tests,\n\n## Fields\n\nBoolean fields with names that start with `is_`, `has_`, `had_`, `was_`, `were_` and `have_` also get a short getter. For example, if field name is `is_awesome`, builder will product two getters: `getIsAwesome()` and `isAwesome()`.\n\n### Password Field\n\nPassword field is field meant for storing password hashes. By default, it sets `password` as field name. It is similar to `StringField` (uses `VARCHAR` columns), but it can't have default value (doh!), and it does not have methods for easy indexing (you can still add an index by yourself, if you wish).\n\n```php\n\u003c?php\n\nnamespace MyApp;\n\nuse ActiveCollab\\DatabaseStructure\\Field\\Scalar\\PasswordField;\n\nnew PasswordField(); // Use default name (password).\nnew PasswordField('psswd_hash'); // Specify field name. \n```\n\n### JSON Field\n\nJSON field add a JSON field to the type. It will be automatically serialized and deserialized on reads and writes:\n\n```php\n$this-\u003eaddType('stats_snapshots')-\u003eaddFields(\n    new JsonField('stats')\n);\n```\n\nOn top of regular getters and setters, JSON fields add a `modify` method. This method receives a callback that will be called with decoded JSON value. Result of the callback is then stored in the field automatically:\n\n```php\n$object-\u003emodifyStats(\n    function ($stats) {\n        $stats['something-to-add'] = true;\n        unset($stats['something-to-remove']);\n        \n        return $stats;\n    }\n);\n```\n\nJSON fields can store a lot of different data types, so you can't always known which type will be passed to the callback. In our everyday use we noticed that arrays are most common data types that are stored in JSON fields. To ensure that you always get an array, regardless of what is in the field, pass in the second `$force_array` argument:\n\n```php\n$object-\u003emodifyStats(\n    function (array $this_will_be_array_for_sure) {\n        return $this_will_be_array_for_sure;\n    },\n    true\n);\n```\n\nSystem supports value extraction from JSON fields. These values are extracted by MySQL automatically, and they can be stored and indexed. \n\nThere are two ways of adding extractors. First is by constructing extractor instance by yourself, and adding it:\n\n```php\n$execution_time_extractor = (new FloatValueExtractor('execution_time', '$.exec_time', 0))\n    -\u003estoreValue()\n    -\u003eaddIndex();\n\n$this-\u003eaddType('stats_snapshots')-\u003eaddFields(\n    new DateField('day'),\n    (new JsonField('stats'))\n        -\u003eaddValueExtractor($execution_time_extractor)\n);\n```\n\nSecond is by calling `extractValue` method, which uses provided arguments to construct the appropriate extractor, configure it and add it to the field. Method arguments:\n\n1. `field_name` - Name of the generated field,\n1. `expression` - Expression used to extract the value from JSON. See [https://dev.mysql.com/doc/refman/5.7/en/json-search-functions.html#function_json-extract](JSON_EXTRACT()) MySQL function for details,\n1. `default_value` - Value that will be used if `expression` returns `NULL`,\n1. `extractor_type` - Class name of the extractor implementation that should be used. Default is `ValueExtractor` (string value extractor), but there are also extractors for int, float, bool, date, and date and time values,\n1. `is_stored` - Should the value be permanently stored, or should it be virtual (calculated on the fly on read). Value is stored by default,\n1. `is_indexed` - Should the value be indexed. Index on the generated field is added when `TRUE`. `FALSE` by default.\n\nExample:\n\n```php\n$this-\u003eaddType('stats_snapshots')-\u003eaddFields(\n    new DateField('day'),\n    (new JsonField('stats'))\n        -\u003eextractValue('plan_name', '$.plan_name', 'Unknown', ValueExtractor::class, true, true)\n        -\u003eextractValue('number_of_active_users', '$.users.num_active', 0, IntValueExtractor::class, true)\n        -\u003eextractValue('is_used_on_day', '$.is_used_on_day', null, BoolValueExtractor::class, false),\n);\n```\n\nGetter methods are automatically added for all generated fields:\n\n```php\n$snapshot = $pool-\u003egetById(StatsSnapshot::class, 1);\nprint $snapshot-\u003egetPlanName() . \"\\n\";\nprint $snapshot-\u003egetNumberOfActiveUsers() . \"\\n\";\nprint ($snapshot-\u003eisUsedOnDay() ? 'yes' : 'no') . \"\\n\";\n```\n\nNote that values of generated fields can't be set directly. This code will raise an exception:\n\n```php\n$snapshot = $pool-\u003egetById(StatsSnapshot::class, 1);\n$snapshot-\u003esetFieldValue('number_of_active_users', 123);  // Exception!\n```\n\n## Associations\n\n### Belongs To\n\n#### Programming to an Interface\n\nBelongs To association supports \"programming to an interface\" approach. This means that you can set so it accepts (and returns) instances that implement a specific interface:\n\n```php\n\u003c?php\n\nnamespace MyApp;\n\nuse ActiveCollab\\DatabaseStructure\\Association\\BelongsToAssociation;\n\n(new BelongsToAssociation('author'))-\u003eaccepts(AuthorInterface::class);\n```\n\n### Has Many\n\n```php\n\u003c?php\n\nnamespace App;\n\nuse ActiveCollab\\DatabaseStructure\\Association\\BelongsToAssociation;\nuse ActiveCollab\\DatabaseStructure\\Association\\HasManyAssociation;\nuse ActiveCollab\\DatabaseStructure\\Field\\Composite\\NameField;\nuse ActiveCollab\\DatabaseStructure\\Structure;\n\nclass HasManyExampleStructure extends Structure\n{\n    public function configure(): void\n    {\n        $this-\u003eaddType('writers')-\u003eaddFields(\n            (new NameField('name', ''))-\u003erequired(),\n        )-\u003eaddAssociations(\n            new HasManyAssociation('books'),\n        );\n        \n        $this-\u003eaddType('books')-\u003eaddFields(\n            (new NameField('name', ''))-\u003erequired(),\n        )-\u003eaddAssociations(\n            new BelongsToAssociation('writer'),\n        );\n    }\n}\n```\n\nMethod that this association will add to `Writer` model are:\n\n* `getBooksFinder(): FinderInterface` - Prepare a book finder instance for this writer, with all the defaults set (ordering for example). Use it like you would use any other finder: extend it with extra conditions, use it to count records, fetch all, or first record etc, \n* `getBooks(): ?iterable` - Return all books that belong to the writer. When no books are found, this method returns `NULL`,\n* `getBookIds(): ?iterable` - Return a list of all book ID-s that belong to the writer. When no books are found, this method returns `NULL`,\n* `countBooks(): int` - Return a total number of books.\n\n#### Attributes\n\nHas many association also adds following attributes to the model:\n\n* `books` - Set associated books by providing their instances. These instances can be persisted to the database, or they can be new instances. If new, they will be saved when parent writer object is saved,\n* `book_ids` - Set associated books by providing their ID-s.\n\n```php\n\u003c?php\n\nnamespace App;\n\n// Set books using an attribute:\n$writer = $pool-\u003eproduce(Writer::class, [\n    'name' =\u003e 'Leo Tolstoy',\n    'books' =\u003e [$book1, $book2, $book3],\n]);\n\n// Or, using ID-s:\n$writer = $pool-\u003eproduce(Writer::class, [\n    'name' =\u003e 'Leo Tolstoy',\n    'book_ids' =\u003e [1, 2, 3, 4],\n]);\n```\n\n#### Programming to an Interface\n\nHas Many association support \"programming to an interface\" approach. This means that you can set so it accepts (and returns) instances that implement a specific interface:\n\nExample:\n\n```php\n\u003c?php\n\nnamespace MyApp;\n\nuse ActiveCollab\\DatabaseStructure\\Association\\HasManyAssociation;\n\n(new HasManyAssociation('books'))-\u003eaccepts(BookInterface::class);\n```\n\n### Has One\n\n#### Programming to an Interface\n\nHas One association support \"programming to an interface\" approach. This means that you can set so it accepts (and returns) instances that implement a specific interface:\n\nExample:\n\n```php\n\u003c?php\n\nnamespace MyApp;\n\nuse ActiveCollab\\DatabaseStructure\\Association\\HasOneAssociation;\n\n(new HasOneAssociation('book'))-\u003eaccepts(BookInterface::class);\n```\n\n### Has Many Via\n\n### Has and Belongs to Many\n\n```php\n\u003c?php\n\nnamespace App;\n\nuse ActiveCollab\\DatabaseStructure\\Association\\HasAndBelongsToManyAssociation;\nuse ActiveCollab\\DatabaseStructure\\Field\\Composite\\NameField;\nuse ActiveCollab\\DatabaseStructure\\Structure;\n\nclass HasManyExampleStructure extends Structure\n{\n    public function configure(): void\n    {\n        $this-\u003eaddType('writers')-\u003eaddFields(\n            (new NameField('name', ''))-\u003erequired(),\n        )-\u003eaddAssociations(\n            new HasAndBelongsToManyAssociation('books'),\n        );\n\n        $this-\u003eaddType('books')-\u003eaddFields(\n            (new NameField('name', ''))-\u003erequired(),\n        )-\u003eaddAssociations(\n            new HasAndBelongsToManyAssociation('writers'),\n        );\n    }\n}\n```\n\nMethod that this association will add to `Writer` model are:\n\n* `getBooksFinder(): FinderInterface` - Prepare a book finder instance for this writer, with all the defaults set (ordering for example). Use it like you would use any other finder: extend it with extra conditions, use it to count records, fetch all, or first record etc, \n* `getBooks(): ?iterable` - Return all books that belong to the writer. When no books are found, this method returns `NULL`,\n* `getBookIds(): ?iterable` - Return a list of all book ID-s that belong to the writer. When no books are found, this method returns `NULL`,\n* `countBooks(): int` - Return a total number of books,\n* `\u0026addBooks(...$books): void` - Add one or more books to the writer,\n* `\u0026removeBooks(...$books): void` - Remove one or more books that are associated with the writer,\n* `\u0026clearBooks(): void` - Clear all book connections that are associated with a writer (book objects are not removed).\n\n#### Attributes\n\nHas and belongs to many association also adds following attributes to the model:\n\n* `books` - Set associated books by providing their instances. These instances can be persisted to the database, or they can be new instances. If new, they will be saved when parent writer object is saved,\n* `book_ids` - Set associated books by providing their ID-s.\n\n```php\n\u003c?php\n\nnamespace App;\n\n// Set books using an attribute:\n$writer = $pool-\u003eproduce(Writer::class, [\n    'name' =\u003e 'Leo Tolstoy',\n    'books' =\u003e [$book1, $book2, $book3],\n]);\n\n// Or, using ID-s:\n$writer = $pool-\u003eproduce(Writer::class, [\n    'name' =\u003e 'Leo Tolstoy',\n    'book_ids' =\u003e [1, 2, 3, 4],\n]);\n```\n\n## Structure Options\n\nStructure object support config option setting via `setConfig()` method. This method can be called during object configuration, of after it has been created:\n\n```php\nclass MyStructure extends Structure\n{\n    public function configure(): void\n    {\n        $this-\u003esetConfig('option_name', 'value');\n    }\n}\n```\n\nFollowing options are available:\n\n1. `add_permissions` - Add CRUD permission checks to objects. [More…](#add_permissions),\n1. `base_class_doc_block_properties` - Specify an array of properties to be added as `@property` elements to DocBlock section of generated classes. [More…](#class_doc_block_properties).\n1. `base_class_extends` - Specify which class should built objects extend (`ActiveCollab\\DatabaseObject\\Object` is default),\n\n### `add_permissions`\n\nThis option tells structure to automatically call `permissions()` method for all types that are added to it. This option is turned off by default, but it can be enabled by setting it to one of the two values: \n\n1. `StructureInterface::ADD_PERMISSIVE_PERMISSIONS` enables permissions and methods that check permissions are set to return `true` by default; \n2. `StructureInterface::ADD_RESTRICTIVE_PERMISSIONS` enables permissions and methods that check permissions are set to return `false` by default.\n\nExample:\n\n```php\nclass MyStructure extends Structure\n{\n    public function configure(): void\n    {\n        $this-\u003esetConfig(‘add_permissions’, StructureInterface::ADD_RESTRICTIVE_PERMISSIONS);\n    }\n}\n```\n\n### `base_class_doc_block_properties`\n\nSome editors read `@property` from DocBlock section of the class and know which properties are available via magic methods, which type they are and offer various features based on that info (like code completion, type checking etc). Use `base_class_doc_block_properties` to specify a list of properties that will be added to the class. Example of the config:\n\n```php\nclass MyStructure extends Structure\n{\n    public function configure(): void\n    {\n        $this-\u003esetConfig(‘base_class_doc_block_properties’, [\n            'jobs' =\u003e '\\\\ActiveCollab\\\\JobsQueue\\\\Dispatcher'\n        ]);\n    }\n}\n```\n\nwhat it builds:\n\n```php\n\u003c?php\n\nnamespace Application\\Structure\\Namespace\\Base;\n\n/**\n * @property \\ActiveCollab\\JobsQueue\\Dispatcher $jobs\n *\n * …\n */\nabstract class Token extends \\ActiveCollab\\DatabaseObject\\Entity\\Entity\n{\n}\n```\n\n### `deprecate_long_bool_field_getter`\n\nSet to true if you want to have log boolean field getters to be marked as deprecated, when there's a short getter (`isAwesome()` vs `getIsAwesome()`).\n\n### `header_comment`\n\nAdd a comment that will be included at the header of all auto-generated files. This option is useful if you need to include licensing information in your source code.\n\n## Behaviours\n\nBehaviours are interfaces and interface implementations that types and fields add to resulting object classes. These behaviours can do all sort of things: let you element position in collections, store additional bits of information on object level, check user permissions and more.\n\n### Permissions Behaviour\n\nWhen applied, permissions behaviour adds `ActiveCollab\\DatabaseStructure\\Behaviour\\PermissionsInterface` to object classes, which add four methods that check user permissions over a given object:\n\n1. `canCreate($user)`\n2. `canView($user)`\n3. `canEdit($user)`\n4. `canDelete($user)`\n\nAll four methods accept only one argument, and that argument needs to be instance that implements `\\ActiveCollab\\User\\UserInterface` interface.\n\nThere are two default implementations that can be added as implementations of `PermissionsInterface`:\n\n1. `ActiveCollab\\DatabaseStructure\\Behaviour\\PermissionsInterface\\PermissiveImplementation` is set to return `true` by default,\n2. `ActiveCollab\\DatabaseStructure\\Behaviour\\PermissionsInterface\\RestrictiveImplementation` is set to return `false` by default.\n\n**Note:** Generated code does not enforce these checks prior to doing CRUD operations. It’s up to the application that includes DatabaseStructure library to enforce that these restrictions are applied (in ACL or controller layer for example).\n\nStructure can be configured to apply permissions behaviour to types automatically (see `add_permissions` structure option). In a situation when you have structure set to automatically add permissions behaviour to types, but you want to turn it off for a particular type, just call `permissions(false)` again:\n\n```php\nclass MyStructure extends Structure\n{\n    public function configure(): void\n    {\n        $this-\u003esetConfig(‘add_permissions’, StructureInterface::ADD_RESTRICTIVE_PERMISSIONS);\n        \n        $this-\u003eaddType(‘reverted_elements’)\n            -\u003eaddFields()\n            -\u003epermissions(false);\n    }\n}\n```\n\n### Protected Fields Behaviour\n\nThis behaviour adds a simple list of proteected fields to the object (accessible using `getProtectedFields()` method). It's up to the rest of the system to decide what to do with this list, but most common scenario is to disable set of these fields when objects are added using POST or updated using PUT requests:\n\n```php\nclass MyStructure extends Structure\n{\n    public function configure(): void\n    {\n        $this-\u003eaddType('elements')-\u003eprotectFields('created_at', 'created_by_id')-\u003eunprotectFields('created_by_id'); // will record ['created_at']\n    }\n}\n```\n\n`protectFields` ignores empty fields values, and it can be called multiple times:\n\n```php\nclass MyStructure extends Structure\n{\n    public function configure(): void\n    {\n        $this-\u003eaddType('elements')-\u003eprotectFields('field_1', 'field_2')-\u003eprotectFields('', '')-\u003eprotectFields('field_2', 'field_3'); // will only record ['field_1', 'field_2', 'field_3']\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factivecollab%2Fdatabasestructure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Factivecollab%2Fdatabasestructure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factivecollab%2Fdatabasestructure/lists"}