{"id":14983867,"url":"https://github.com/symfony-orchestra/view-bundle","last_synced_at":"2025-04-09T22:17:31.114Z","repository":{"id":226188234,"uuid":"767435075","full_name":"symfony-orchestra/view-bundle","owner":"symfony-orchestra","description":"The `view-bundle` is a simple and highly efficient Symfony bundle designed to replace Symfony Responses when working with the JSON API.","archived":false,"fork":false,"pushed_at":"2024-04-01T08:21:09.000Z","size":40,"stargazers_count":231,"open_issues_count":0,"forks_count":22,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-09T22:17:23.234Z","etag":null,"topics":["api","api-rest","json","symfony","symfony-bundle"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/symfony-orchestra.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,"publiccode":null,"codemeta":null}},"created_at":"2024-03-05T09:38:29.000Z","updated_at":"2025-04-02T22:47:21.000Z","dependencies_parsed_at":"2024-04-01T09:30:44.577Z","dependency_job_id":"c39dc525-4925-4cb3-bc58-11273c09c9af","html_url":"https://github.com/symfony-orchestra/view-bundle","commit_stats":{"total_commits":5,"total_committers":1,"mean_commits":5.0,"dds":0.0,"last_synced_commit":"9fa60f1af55557d9165074f752ac9e6d8a65ecfb"},"previous_names":["symfony-orchestra/view-bundle"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/symfony-orchestra%2Fview-bundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/symfony-orchestra%2Fview-bundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/symfony-orchestra%2Fview-bundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/symfony-orchestra%2Fview-bundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/symfony-orchestra","download_url":"https://codeload.github.com/symfony-orchestra/view-bundle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248119286,"owners_count":21050755,"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":["api","api-rest","json","symfony","symfony-bundle"],"created_at":"2024-09-24T14:08:05.746Z","updated_at":"2025-04-09T22:17:31.091Z","avatar_url":"https://github.com/symfony-orchestra.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# view-bundle\n\nThe `view-bundle` is a simple and highly efficient Symfony bundle designed to replace Symfony Responses when working with the JSON API.\n\nThe goal of the bundle is to separate the response views from the app's business logic, making them typed, configurable, and reusable across the app.\n\nAs a result, you will have a set of simple `View` classes with an internal hierarchy that is easily understandable by everybody in a team.\n\n# Requirements\n\n- PHP 8.3 \n- Symfony 7.0.*\n- Doctrine common ^3.4\n\n```json\n{\n  \"php\": \"^8.3\",\n  \"symfony/http-kernel\": \"7.0.*\",\n  \"symfony/serializer\": \"7.0.*\",\n  \"symfony/property-access\": \"7.0.*\",\n  \"symfony/dependency-injection\": \"7.0.*\",\n  \"symfony/config\": \"7.0.*\",\n  \"doctrine/common\": \"^3.4.3\"\n}\n```\n\n# Example\n\nLet's consider the code below as an example.\nWe have an entity `User` with some fields and with the joined collection of `Image` images.\n\n\n```php\n\u003c?php\ndeclare(strict_types=1);\n\nclass User\n{\n    public Uuid $id;\n    public string|null $firstName = null;\n    public string|null $lastName = null;\n    public iterable $images = [];\n}\n\n```\n```php\n\u003c?php\ndeclare(strict_types=1);\n\nclass Image\n{\n    public Uuid $id;\n    private User $user;\n    private string $path;\n}\n\n```\n\nThe possible views for our scenario could be:\n\n```php\n\u003c?php\ndeclare(strict_types=1);\n\nuse SymfonyOrchestra\\ViewBundle\\Attribute\\Type;\n\nclass UserView extends BindView\n{\n    public Uuid $id;\n    public string|null $firstName;\n    public string|null $lastName;\n    \n    /** It will be transformed into array of ImageViews */\n    #[Type(ImageView::class)]\n    public IterableView $images;\n    \n    /** It's a custom property which does not exist in the User class */\n    public \\DateTimeImmutable $notBoundField;\n\n    public function __construct(User $user)\n    {\n        parent::__construct($user);\n        $this-\u003enotBoundField = $user-\u003egetCreatedDatetime();\n    }\n}\n\n```\n\n```php\n\u003c?php\ndeclare(strict_types=1);\n\nclass ImageView extends BindView\n{\n    public Uuid $id;\n    public string $path;\n}\n\n```\n\nAs a result, the following request for the current user with the name \"Andrew\", an empty last name, and some pictures of the orchestra \n\n```php\n\u003c?php\ndeclare(strict_types=1);\n\n#[Route('/user/me', methods: ['GET'], priority: 1)]\n#[IsGranted('ROLE_USER')]\nclass GetMeAction extends GetAction\n{\n    public function __invoke(Request $request): ViewInterface\n    {\n        return new UserView($this-\u003egetUser());\n    }\n}\n```\n\nWill produce the following 200 response\n\n```json\n{\n  \"data\": {\n    \"id\": \"92c7c4d4-2ce0-4353-a9e2-6a3794c60d8f\",\n    \"firstName\": \"Andrew\",\n    \"images\": [\n      {\n        \"id\": \"eb9fa57e-3d8f-44c5-80d4-7f33220f1a48\",\n        \"path\": \"/grand-piano.png\"\n      },\n      {\n        \"id\": \"16d01967-9066-4dc9-9d82-028419ba0ed5\",\n        \"path\": \"/violin.png\"\n      }\n    ],\n    \"notBoundField\": \"1685-03-31\"\n  }\n}\n\n```\n\nThe response is fully controllable, you can still add different headers to the response using the stack of provided internal View classes (`ResponseView`).\n\nThe main payload is placed under the `data` key in the JSON array.\n\nAs you can see, the last name is omitted because `null` values were removed from the response to match with `undefined` properties while working with a `Typescript`. \n\n# Installation\n\n```\ncomposer install symfony-orchestra/view-bundle:7.0.*\n```\n\nAdd the bundle to `config/bundles.php`\n```php\n\u003c?php\n\nreturn [\n    /** ... */\n    SymfonyOrchestra\\ViewBundle\\DevViewBundle::class =\u003e ['all' =\u003e true],\n];\n\n\n```\n\nTo make it work your controller should return an object of instance of `SymfonyOrchestra\\ViewBundle\\View\\ViewInterface` instead of `Symfony\\Component\\HttpFoundation\\Response`.\n\n# Cache\n\nThe most usable `SymfonyOrchestra\\ViewBundle\\View\\BindView` which maps the properties of the class with the properties of the view comes with the cache support.\nSee `SymfonyOrchestra\\ViewBundle\\EventSubscriber\\SetVersionSubscriber` for more details.\nIt uses `Symfony\\Component\\PropertyAccess\\PropertyAccessor::createCache` when the env parameter `APP_DEBUG` is set to `false`.\n\n\n# Internal views\n\nThe bundle comes with the several internal core views.   \n\n### \\SymfonyOrchestra\\ViewBundle\\View\\ResponseView \n\nThe main view that can be considered as a response. Contains headers and http status that can be overridden.\nSee `SymfonyOrchestra\\EventSubscriber\\ViewSubscriber`.\n\n### \\SymfonyOrchestra\\ViewBundle\\View\\DataView \n\nThe inherited view of the `ResponseView`, that wraps all the data into `data` JSON key.\nSee `SymfonyOrchestra\\EventSubscriber\\ViewSubscriber`.\n\n### \\SymfonyOrchestra\\ViewBundle\\View\\BindView\n\nThe helper View that maps the properties of the underlined object to the view as one to one. The most powerful one.\nIt uses `SymfonyOrchestra\\ViewBundle\\Utils\\BindUtils` internally to map the properties.\n\n```php\nclass User {\n    private int $int;\n    private string $string;\n    private iterable $collection\n}\n\nclass UserView extends \\SymfonyOrchestra\\ViewBundle\\View\\BindView {\n    /** will take all the properties from the User class */\n    private int $int;\n    private string $string;\n    private array $collection\n}\n```\n\n### \\SymfonyOrchestra\\ViewBundle\\View\\IterableView\n\nThe view for the iterable objects.\n\n```php\n\nclass GetOptions  extends GetAction\n{\n    public function __invoke(Request $request): ViewInterface\n    {\n        $option1 = new Option();\n        $option2 = new Option();\n        return new \\SymfonyOrchestra\\ViewBundle\\View\\IterableView(\n            [$option1, $option2],\n            OptionView::class,\n        );\n    }\n}\n\n```\n\n\nIt can be used together with the `\\SymfonyOrchestra\\ViewBundle\\Attribute\\Type` and `\\SymfonyOrchestra\\ViewBundle\\View\\BindView`\nattribute to simplify the workflow. In this case the underlined iterable objects will be automatically constructed based on the configured\ntype. \n\n**Enjoy the orchestra! 🎻**","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsymfony-orchestra%2Fview-bundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsymfony-orchestra%2Fview-bundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsymfony-orchestra%2Fview-bundle/lists"}