precision-soft / symfony-json-form
forms that serialize to json for frontend frameworks with symfony api backends
v1.0.0
2024-09-18 10:06 UTC
Requires
- php: >=8.2
- symfony/http-foundation: 7.*
- symfony/serializer: 7.*
- symfony/translation-contracts: 3.*
Requires (Dev)
README
You may fork and modify it as you wish.
Any suggestions are welcomed.
Purpose
The purpose of this library is to create forms for single page applications, with a symfony backend. The forms are constructed in the backend and serialized to json, that can be rendered in the frontend. In the assets folder you can find a react component to render the form. There are 2 versions of the form:
- formV1, is the original react components for rendering the form, kept for backwards compatibility.
- formV2, the new components and the recommended way to render the json.
Usage
Add this to your services.yaml.
services:
_instanceof:
PrecisionSoft\Symfony\JsonForm\Service\Contract\AbstractFormService:
calls:
- [ setSerializer, [ '@serializer' ] ]
<?php
declare(strict_types=1);
/*
* Copyright (c) Vivre
*/
namespace Acme\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Acme\Dto\ProductEditDto;
use Acme\Form\ProductEditForm;
use Acme\Service\ProductEditService;
class ProductController extends AbstractController
{
public function edit(Request $request, ProductEditForm $productEditForm, ProductEditService $productEditService): Response
{
$id = $request->get('id');
if (Request::METHOD_POST === $request->getMethod()) {
/** @var ProductEditDto $dto */
$dto = $productEditForm->handleRequest($request);
$productEditService->save($dto);
} else {
$dto = $productEditService->createDto($id);
}
return $this->json(
[
'form' => $productEditForm->render($dto),
]
);
}
}
<?php
declare(strict_types=1);
/*
* Copyright (c) Vivre
*/
namespace Acme\Dto;
use PrecisionSoft\Symfony\JsonForm\Contract\DtoInterface;
class ProductEditDto implements DtoInterface
{
private int $id;
private string $status;
public function getId(): int
{
return $this->id;
}
public function setId(int $id): self
{
$this->id = $id;
return $this;
}
public function getStatus(): string
{
return $this->status;
}
public function setStatus(string $status): self
{
$this->status = $status;
return $this;
}
}
<?php
declare(strict_types=1);
/*
* Copyright (c) Vivre
*/
namespace Acme\Form;
use Acme\Dto\ProductEditDto;
use PrecisionSoft\Symfony\JsonForm\Element\ArrayElement;
use PrecisionSoft\Symfony\JsonForm\Element\NumberElement;
use PrecisionSoft\Symfony\JsonForm\Form\Action;
use PrecisionSoft\Symfony\JsonForm\Form\Form;
use PrecisionSoft\Symfony\JsonForm\Service\Contract\AbstractFormService;
class ProductEditForm extends AbstractFormService
{
protected function getDtoClass(): string
{
return ProductEditDto::class;
}
protected function getAction(): Action
{
return new Action('product-edit');
}
protected function build(Form $form): void
{
$form->addElement(new NumberElement('id', 'Id'))
->addElement(new ArrayElement('status', 'Status', ['active' => 'Active', 'inactive' => 'Inactive']));
}
}
<?php
declare(strict_types=1);
/*
* Copyright (c) Vivre
*/
namespace Acme\Service;
use Acme\Dto\ProductEditDto;
class ProductEditService
{
public function createDto(int $id): ProductEditDto
{
$dto = new ProductEditDto();
$dto->setId($id);
/* @todo populate all the data from the db */
return $dto;
}
public function save(ProductEditDto $dto): void
{
}
}
React
Use the components from ./assets/react folder to interpret the backend response. The Config component is project specific. For me, it holds the locale context of the application. It is integrated with:
willdurand/js-translation-bundle
for the Translator.friendsofsymfony/jsrouting-bundle
for the UrlGenerator.
Dev
git clone git@gitlab.com:precision-soft-open-source/symfony/json-form.git
cd json-form
./dc build && ./dc up -d
Todo
- Render and handle complex types like \DateTime.
- Unit tests.