AI prompts
base on The `view-bundle` is a simple and highly efficient Symfony bundle designed to replace Symfony Responses when working with the JSON API. # view-bundle
The `view-bundle` is a simple and highly efficient Symfony bundle designed to replace Symfony Responses when working with the JSON API.
The 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.
As a result, you will have a set of simple `View` classes with an internal hierarchy that is easily understandable by everybody in a team.
# Requirements
- PHP 8.3
- Symfony 7.0.*
- Doctrine common ^3.4
```json
{
"php": "^8.3",
"symfony/http-kernel": "7.0.*",
"symfony/serializer": "7.0.*",
"symfony/property-access": "7.0.*",
"symfony/dependency-injection": "7.0.*",
"symfony/config": "7.0.*",
"doctrine/common": "^3.4.3"
}
```
# Example
Let's consider the code below as an example.
We have an entity `User` with some fields and with the joined collection of `Image` images.
```php
<?php
declare(strict_types=1);
class User
{
public Uuid $id;
public string|null $firstName = null;
public string|null $lastName = null;
public iterable $images = [];
}
```
```php
<?php
declare(strict_types=1);
class Image
{
public Uuid $id;
private User $user;
private string $path;
}
```
The possible views for our scenario could be:
```php
<?php
declare(strict_types=1);
use SymfonyOrchestra\ViewBundle\Attribute\Type;
class UserView extends BindView
{
public Uuid $id;
public string|null $firstName;
public string|null $lastName;
/** It will be transformed into array of ImageViews */
#[Type(ImageView::class)]
public IterableView $images;
/** It's a custom property which does not exist in the User class */
public \DateTimeImmutable $notBoundField;
public function __construct(User $user)
{
parent::__construct($user);
$this->notBoundField = $user->getCreatedDatetime();
}
}
```
```php
<?php
declare(strict_types=1);
class ImageView extends BindView
{
public Uuid $id;
public string $path;
}
```
As a result, the following request for the current user with the name "Andrew", an empty last name, and some pictures of the orchestra
```php
<?php
declare(strict_types=1);
#[Route('/user/me', methods: ['GET'], priority: 1)]
#[IsGranted('ROLE_USER')]
class GetMeAction extends GetAction
{
public function __invoke(Request $request): ViewInterface
{
return new UserView($this->getUser());
}
}
```
Will produce the following 200 response
```json
{
"data": {
"id": "92c7c4d4-2ce0-4353-a9e2-6a3794c60d8f",
"firstName": "Andrew",
"images": [
{
"id": "eb9fa57e-3d8f-44c5-80d4-7f33220f1a48",
"path": "/grand-piano.png"
},
{
"id": "16d01967-9066-4dc9-9d82-028419ba0ed5",
"path": "/violin.png"
}
],
"notBoundField": "1685-03-31"
}
}
```
The response is fully controllable, you can still add different headers to the response using the stack of provided internal View classes (`ResponseView`).
The main payload is placed under the `data` key in the JSON array.
As 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`.
# Installation
```
composer install symfony-orchestra/view-bundle:7.0.*
```
Add the bundle to `config/bundles.php`
```php
<?php
return [
/** ... */
SymfonyOrchestra\ViewBundle\DevViewBundle::class => ['all' => true],
];
```
To make it work your controller should return an object of instance of `SymfonyOrchestra\ViewBundle\View\ViewInterface` instead of `Symfony\Component\HttpFoundation\Response`.
# Cache
The most usable `SymfonyOrchestra\ViewBundle\View\BindView` which maps the properties of the class with the properties of the view comes with the cache support.
See `SymfonyOrchestra\ViewBundle\EventSubscriber\SetVersionSubscriber` for more details.
It uses `Symfony\Component\PropertyAccess\PropertyAccessor::createCache` when the env parameter `APP_DEBUG` is set to `false`.
# Internal views
The bundle comes with the several internal core views.
### \SymfonyOrchestra\ViewBundle\View\ResponseView
The main view that can be considered as a response. Contains headers and http status that can be overridden.
See `SymfonyOrchestra\EventSubscriber\ViewSubscriber`.
### \SymfonyOrchestra\ViewBundle\View\DataView
The inherited view of the `ResponseView`, that wraps all the data into `data` JSON key.
See `SymfonyOrchestra\EventSubscriber\ViewSubscriber`.
### \SymfonyOrchestra\ViewBundle\View\BindView
The helper View that maps the properties of the underlined object to the view as one to one. The most powerful one.
It uses `SymfonyOrchestra\ViewBundle\Utils\BindUtils` internally to map the properties.
```php
class User {
private int $int;
private string $string;
private iterable $collection
}
class UserView extends \SymfonyOrchestra\ViewBundle\View\BindView {
/** will take all the properties from the User class */
private int $int;
private string $string;
private array $collection
}
```
### \SymfonyOrchestra\ViewBundle\View\IterableView
The view for the iterable objects.
```php
class GetOptions extends GetAction
{
public function __invoke(Request $request): ViewInterface
{
$option1 = new Option();
$option2 = new Option();
return new \SymfonyOrchestra\ViewBundle\View\IterableView(
[$option1, $option2],
OptionView::class,
);
}
}
```
It can be used together with the `\SymfonyOrchestra\ViewBundle\Attribute\Type` and `\SymfonyOrchestra\ViewBundle\View\BindView`
attribute to simplify the workflow. In this case the underlined iterable objects will be automatically constructed based on the configured
type.
**Enjoy the orchestra! 🎻**", Assign "at most 3 tags" to the expected json: {"id":"9215","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"