{"id":19632093,"url":"https://github.com/demos-europe/edt","last_synced_at":"2025-04-28T06:33:40.500Z","repository":{"id":38540270,"uuid":"504560960","full_name":"demos-europe/edt","owner":"demos-europe","description":"Enables your PHP application to expose its entities as REST resources using the feature rich JSON:API specification as API. How and to whom your entities are exposed is highly customizable while minimizing boilerplate code.","archived":false,"fork":false,"pushed_at":"2024-12-19T12:54:18.000Z","size":2571,"stargazers_count":2,"open_issues_count":69,"forks_count":1,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-04-05T07:23:10.203Z","etag":null,"topics":["crud-api","doctrine-orm","json-api","php","querying","restrict-access","sorting"],"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/demos-europe.png","metadata":{"files":{"readme":"docs/README.adoc","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.adoc","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,"publiccode":null,"codemeta":null}},"created_at":"2022-06-17T14:16:09.000Z","updated_at":"2024-07-04T09:23:52.000Z","dependencies_parsed_at":"2023-02-17T03:46:08.560Z","dependency_job_id":"ff942ac3-bdcf-4328-bd2e-bf28fd4d9177","html_url":"https://github.com/demos-europe/edt","commit_stats":null,"previous_names":[],"tags_count":118,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/demos-europe%2Fedt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/demos-europe%2Fedt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/demos-europe%2Fedt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/demos-europe%2Fedt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/demos-europe","download_url":"https://codeload.github.com/demos-europe/edt/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251266252,"owners_count":21561804,"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":["crud-api","doctrine-orm","json-api","php","querying","restrict-access","sorting"],"created_at":"2024-11-11T12:12:54.892Z","updated_at":"2025-04-28T06:33:36.710Z","avatar_url":"https://github.com/demos-europe.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"= EDT\n:toc:\n:sectanchors:\n\nimage:https://img.shields.io/packagist/v/demos-europe/edt-jsonapi.svg[Packagist Version]\nimage:https://img.shields.io/packagist/php-v/demos-europe/edt-jsonapi.svg[PHP from Packagist]\nimage:https://img.shields.io/librariesio/github/demos-europe/edt.svg[Dependencies]\nimage:https://img.shields.io/badge/phpstan_level-9-green[Phastan-9]\nimage:https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits\u0026logoColor=white[\"Conventional Commits\",link=https://conventionalcommits.org]\n\nThis is the EDT monorepo, containing the development of multiple composer packages.\nIn conjunction these packages can be used in PHP applications (if they already use Doctrine as ORM framework) to define a functional web-API contract, adhering to the https://jsonapi.org/format/1.0/[JSON:API specification].\n\n== About EDT\n\nThis library enables you to expose the entity class instances in your PHP application as REST resources using the feature rich link:https://jsonapi.org/[JSON:API specification] as API contract.\nHow and to whom you expose your entities is highly customizable while minimizing the need of manually written boilerplate code.\n\nWithout custom extensions, usage is currently limited to applications building their entities uppon the link:https://www.doctrine-project.org/projects/orm.html[Doctrine ORM].\nHowever, as the different underlying functionalities are split and encapsulated in many individual PHP classes, put together via constructor-based dependency injection, a high degree of customizability and extensibility is ensured.\n\nThese classes are structured into multiple composer packages contained in this repository, to ease re-usage for different use cases without the need to include large, unnecessary dependencies.\nE.g. instead of using it as a whole, you may want to use parts of this library to build your own API implementation more easily. If you do, you can simply skip the Doctrine ORM dependency and implement your own way to access the data source of your entities.\n\n== When to use\n\nDue to the variety of features provided by the JSON:API specification, implementing it in a maintainable way may provide a challenge.\nAfter an initial setup, this library reduces your duty to expressing your intents regarding your web-API contract once, avoiding the risks of inconsistencies due to redundancy.\n\nThis is especially helpful in large, volatile, fast-growing web-applications with demanding clients, where most new business features may require new entities or properties to be exposed to them.\n\nOn the other hand, for small and relatively settled applications, this library may not be the right choice. This is due to the currently notable time cost for the initial setup, especially in case of applications that don't already use Symfony and the Doctrine ORM framework. At some point the setup difficulty may be reduced, but for now, batteries are *not* included.\n\n== How to use\n\nNOTE: The xref:initial-setup.adoc[initial setup] is quite complex and a considerable entry barrier.\nBut as it only needs to be done once, this section will attempt to give an idea of the more relevant typical day-to-day usage instead.\n\nWhen you express your intent regarding your API's capabilities and schema with this library, it is logically centered around the resources and their properties.\n\nFor example, if you wanted to expose your `Book` entity of your bookstore, you'd write a corresponding PHP configuration, defining which properties of the `Book` *entity* are to be available via `Book` *resources*.\nFor the following example we assume that you also want to expose `Person` *entities* as `Author` *resources*, with each book having potentially multiple authors.\n\n[mermaid]\nifdef::env-github[[source,mermaid]]\n....\nerDiagram\n    Book {\n        string id\n        string title\n        int price\n    }\n    Person {\n        string id\n        string fullName\n    }\n    Person ||--o{ Book : books\n    Book ||--o{ Person : authors\n....\n\nThe library allows for xref:implementing-types.adoc[multiple approaches] to define your resources.\nThe following code shows an example how to use the highest level of abstraction provided by the library: type config classes.\nUsing these you can create a configuration that will be applied to all resources of a specific type (i.e. using the same value in their `type` field).\n\nAfter creating the configuration instance you have access to methods to set some general information and behavior of the type, but you can also configure the schema of the type by accessing  the corresponding properties on the type config instance.\n\n[source,php]\n----\n$bookConfig = new BookBasedResourceConfig($propertyFactory); \u003c1\u003e\n$authorConfig = new PersonBasedResourceConfig($propertyFactory); \u003c2\u003e\n\n$bookConfig-\u003eid\n    -\u003esetReadableByPath();\n$bookConfig-\u003etitle \u003c3\u003e\n    -\u003esetReadableByPath()\n    -\u003esetSortable()\n    -\u003esetFilterable();\n$bookConfig-\u003eprice\n    -\u003esetReadableByPath()\n    -\u003esetFilterable()\n    -\u003eaddPathUpdateBehavior(); \u003c4\u003e\n$bookConfig-\u003eauthors\n    -\u003esetRelationshipType($authorConfig) \u003c5\u003e\n    -\u003esetFilterable()\n    -\u003esetReadableByPath()\n    -\u003eaddPathUpdateBehavior();\n\n$authorConfig-\u003esetTypeName('Author'); \u003c6\u003e\n$authorConfig-\u003eid\n    -\u003esetReadableByPath();\n$authorConfig-\u003efullName\n    -\u003esetReadableByPath()\n    -\u003esetFilterable();\n$authorConfig-\u003ebooks\n    -\u003esetRelationshipType($bookConfig) \u003c7\u003e\n    -\u003esetReadableByPath();\n\n----\n\u003c1\u003e You can automatically generate and re-generate a basic configuration template class like `BookBasedResourceConfig` from your entity class (i.e. `Book`).\nThis avoids manually writing and maintaining some boilerplate code and helps for its actual configuration, e.g. by providing proper type hinting and IDE autocompletion support.\n\u003c2\u003e Because resources often reference each other via relationship properties, we first create all configuration instances and configure them afterward.\n\u003c3\u003e Individual properties can be configured using link:https://en.wikipedia.org/wiki/Fluent_interface[fluent, self-referential] methods. I.e. each method call on a property will return that property, so it can be used for further method calls.\n\u003c4\u003e To keep this example small, the `price` attribute and `authors` relationship are the only properties that can be updated via the JSON:API. For the same reason we omitted other features here, like default sorting, limiting access to instances and the creation of resources, which are all possible too.\n\u003c5\u003e When configuration template classes are generated, the `authors` relationship from the `Book` entity to the `Person` entity was automatically deduced, but you may expose your `Person` entity via different resource types, using different configurations each. Thus, you need to define which of them the `Book` resources references, by setting the specific `$authorConfig` configuration.\n\u003c6\u003e By default, the type name of your exposed resources will be the same as the name of the backing entity. This is correct for the `Book` resources, but for the `Author` resources we need to specify the name, otherwise `Person` would be used as resource type name.\n\u003c7\u003e By not only referencing the authors by the `Book` resource but also the books by the `Author` resource, we created a bidirectional relationship, which is not necessary and (without workarounds) requires a corresponding relationship in the `Person` entity, but provides greater flexibility to the client.\n\nWhen a request is received, the library will automatically validate it against the configuration above and deny it, if access violations are detected.\nAccess is only granted as defined, no other entities or properties will be exposed and the exposed properties can only be used as configured.\n\nNOTE: In this basic example we simply exposed all `Book` and `Person` entities that are stored in your data source as `Book` and `Author` resources and didn't distinguish access rights based on authorizations.\nIn practice this may often not be acceptable.\nWe also assumed that all exposed resource properties have a corresponding property in the entity, which is also not always the case.\nConsequently, the configuration classes provide additional methods to control the access and mitigate schema divergences.\nThose methods and others, e.g. to configure the creation of resources, were not shown in the example above.\nFor a more thorough overview of the configuration capabilities as Q\u0026A, please see xref:configuring_resources.adoc[Configuring Resources].\n\nEven though the two resource configurations were mostly done independently, their definition as shown above directly result in some synergies and multiple JSON:API specification features provided to the client, briefly explained in the following sections.\n\n=== Includes and relationships\n\nClients can not only fetch `Book` resources in one request and `Author` resources in another, but also state in the fetch request of one or multiple `Book` resources that referenced `Author` resources shall be included in the response.\nBecause of the bidirectional relationship between books and authors this also works the other way around.\nFrom a security perspective this is of no concern, as it does not expose any additional data, but simply allows to request and use it in a more convenient way.\n\nAlso, setting the `authors` relationship as (for example) updatable will allow to change the list of author, but the values set must be resources corresponding to the exact resource type set via `setResourceType($authorConfig)`.\nOtherwise, the request would be denied.\n\n=== Sorting and filtering\n\nSetting the `fullName` attribute as filterable allows the client to fetch `Author` resources filtered by that attribute.\nBut by setting the `authors` relationship in the `Book` resource as filterable as well, the client can now filter `Book` resources by the name of the corresponding authors of each book.\nThe same is true in regard to sorting `Book` resources by the data stored in to-one relationships, as long as the corresponding properties are set as filterable.\nHowever, sorting by to-many relationships is currently not supported.\nI.e. you could sort `Book` resources by the name of their authors, if every book only has a single author.\nBut you can't do that if a single book has many authors.\n\nWhile the JSON:API specification provides a format to define the intended sorting, it leaves the format to define filters open.\nBy default, this library supports the standard link:https://www.drupal.org/docs/8/modules/jsonapi/filtering[Drupal filter] format, with its shorthands not yet being supported.\nThe Drupal filter format allows to define simple but also complex filters, including nested `AND`/`OR` groups and a set of many different operators.\nThe implementation in this library is able to automatically convert such filters into the corresponding DQL query to fetch Doctrine entities.\nIf wanted, support for custom operators can be added.\nOtherwise, no additional implementation is needed beside configuring resource properties as filterable as shown above.\n\n== Stability and development path\n\nEven though the packages are already used in production, they're not recommended for general usage yet.\nWhile development has settled down in some parts, in others refactorings are still necessary, resulting in deprecations and backward compatibility breaking changes.\nBecause the initial setup still requires extensive work and crucial documentation is missing, the entry barrier can also be deemed too high.\n\nHowever, contributions are welcome:\n\n* https://github.com/demos-europe/edt/issues/new[Create a new issue (e.g. a question, bug report or feature request)]\n* https://github.com/demos-europe/edt/contribute[Work on open issues]\n* https://github.com/demos-europe/edt/pulls?q=is%3Aopen+is%3Apr+milestone%3A0.27.0+label%3APrerequisite[Review prerequisite pull requests] (*current bottleneck*)\n\nThe objective is to get the project to a more stable state over the course of the year 2024, ideally releasing a xref:releasing.adoc#_stable_release[1.0.0 version with a more reliable API and proper documentation] before 2025.\n\nEven after a stable release, adding optional features and support for future JSON:API specification versions is left as an ongoing process. Similarly, easing the usage with applications not based on Symfony and Doctrine is not the scope of a first stable version.\n\n== Credits and acknowledgements\n\nConception and implementation by Christian Dressler with many thanks to link:https://github.com/eFrane[eFrane].\n\nAdditional thank goes to link:https://demos-deutschland.de/[DEMOS plan GmbH], which provided the initial use-case and the opportunity to implement relevant parts to solve it.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdemos-europe%2Fedt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdemos-europe%2Fedt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdemos-europe%2Fedt/lists"}