Appite – validace zanořené entity
- Richard Faila
- Člen | 40
Zdravím
Potřeboval jsem udělat api, tak jsem se rozhodl zkusit appite. Nemělo by to být nic složitého, pár endpoitů, ale bude muset zpracovávat a validovat velké json struktury.
Šel jsem podle dokumentace a zkusil udělat jednoduché vytvoření uživatele.
namespace App\Controllers;
use Apitte\Core\Annotation\Controller\Method;
use Apitte\Core\Annotation\Controller\Path;
use Apitte\Core\Annotation\Controller\Responses;
use Apitte\Core\Annotation\Controller\Response;
use Apitte\Core\Annotation\Controller\RequestBody;
use Apitte\Core\Annotation\Controller\Tag;
use Apitte\Core\Annotation\Controller\RequestParameter;
use Apitte\Core\Annotation\Controller\RequestParameters;
use Apitte\Core\Http\ApiRequest;
use Apitte\Core\Http\ApiResponse;
use App\Entity\Request\User;
use Apitte\Negotiation\Http\ArrayEntity;
/**
* @Path("/user")
*/
final class UserController extends BaseV1Controller
{
/**
* @Path("/create")
* @Method("POST")
* @RequestBody(entity="App\Entity\Request\User")
* @Responses({
* @Response(code="200", description="Success"),
* @Response(code="404", description="Not found")
* })
*/
public function create(ApiRequest $request, ApiResponse $response): ApiResponse
{
/** @var User $entity */
$entity = $request->getEntity();
var_dump($entity);
return $response
->withStatus(ApiResponse::S200_OK)
->withEntity(ArrayEntity::from($entity->toArray()));
}
}
Vstup je
namespace App\Entity\Request;
use Apitte\Core\Mapping\Request\BasicEntity;
use Symfony\Component\Validator\Constraints as Assert;
final class User extends BasicEntity
{
/**
* @var string
*
* @Assert\NotBlank
*/
public $name;
/**
* @var string
*
* @Assert\NotBlank
*/
public $surname;
/**
* @var Address
*
* @Assert\NotBlank
* @Assert\Type("App\Entity\Request\Address")
* @Assert\Valid
*/
public $address;
}
kde je zanořený objekt s adresou (samostatná třída pro další použití):
namespace App\Entity\Request;
use Apitte\Core\Mapping\Request\BasicEntity;
use Symfony\Component\Validator\Constraints as Assert;
final class Address extends BasicEntity
{
/**
* @var string
* @Assert\NotBlank
*/
public $street;
/**
* @var string
* @Assert\NotBlank
*/
public $city;
}
Jenže při pokusu o odeslání:
{
"name": "John",
"surname": "Doe",
"address": {
"street": "Browning street",
"city": "London"
}
}
Dostanu chybu:
{
"status": "error",
"data": {
"code": 422,
"error": "Request body contains an error. See context for details.",
"context": {
"validation": {
"address": [
"This value should be of type App\\Entity\\Request\\Address."
]
}
}
}
}
Pokud z adresy odeberu anotaci s typem, adresa se nevaliduje vůbec. Uniká mi něco? Dík moc.
- Felix
- Nette Core | 1245
To vypada jako vystup ze symfony/validator. Mohl bys napsat jake verze PHP a knihoven pouzivas?
A uplne idealni by bylo, nemohl bys pridat tuhle ukazku do skeletonu (https://github.com/…te-skeleton) Ja bych to pak mohl rovnou vyzkouset.
- Richard Faila
- Člen | 40
PHP 7.4, verze knihoven by měli být nejnovější, vše jsem stahoval
přes composer require
{
"name": "nette/web-project",
"description": "Nette: Standard Web Project",
"keywords": ["nette"],
"type": "project",
"license": ["MIT", "BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
"require": {
"php": ">= 7.4",
"nette/application": "^3.1",
"nette/bootstrap": "^3.1",
"nette/caching": "^3.1",
"nette/database": "^3.1",
"nette/di": "^3.0",
"nette/security": "^3.1",
"nette/utils": "^3.2",
"tracy/tracy": "^2.8",
"contributte/apitte": "^0.9.1",
"nettrine/annotations": "^0.7.0",
"symfony/validator": "^5.4",
"nettrine/cache": "^0.3.0",
"contributte/validator": "^1.1"
},
"require-dev": {
"nette/tester": "^2.3",
"symfony/thanks": "^1"
},
"autoload": {
"psr-4": {
"App\\": "app"
}
},
"minimum-stability": "stable",
"config": {
"allow-plugins": {
"symfony/thanks": true
}
}
}
Na ten skeleton se podívám a pošlu odkaz na repo.
- Richard Faila
- Člen | 40
Konečně jsem se k tomu dostal:
Skeleton vyhazuje 500 a v syslogu je chybová hláška:
[2022-08-12T12:24:17.428305+02:00] default.ERROR: No request entity found {"exception":"[object] (Apitte\\Core\\Exception\\Logical\\InvalidStateException(code: 0): No request entity found at /home/jakub/Web/api/vendor/apitte/core/src/Http/ApiRequest.php:47)"} {"url":"/api/public/v1/user/create","ip":"::1","http_method":"POST","server":"localhost","referrer":null,"file":"/home/jakub/Web/api/vendor/apitte/core/src/ErrorHandler/PsrLogErrorHandler.php","line":34,"class":"Apitte\\Core\\ErrorHandler\\PsrLogErrorHandler","callType":"->","function":"handle","memory_peak_usage":"8 MB","process_id":6809}
Odkaz na repo: https://github.com/…tte-skeleton
- Richard Faila
- Člen | 40
Při testování samostatné komponenty Symfony/Serializer
jsem
přišel na konfiguraci, která data zpracuje tak jak potřebuji, tj, včetně
správného otypování zanořených entit.
Je to stejné jako Serializer, který je použit ve skeletonu, ale je tam
menší změna v konstruktoru ObjectNormalizer
:
new ObjectNormalizer(
new,
new CamelCaseToSnakeCaseNameConverter(),
null,
new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()])
)
Problém je, že nevím jak nejlépe dostat Symfony/Serializer
do apitte (aby zpracovávalo deserializaci entity),
Apitte\Core\DI\Plugin\CoreMappingPlugin
ho nepoužívá (na rozdíl
od skeletonu) a v konfiguraci je jen možnost nastavit Validator. Chápu
správně že budu muset vytvořit vlastní implementaci
CoreMappingPlugin
?
- Richard Faila
- Člen | 40
Ještě pro upřesnění, chyba je ve třídě
Apitte\Core\Mapping\RequestEntityMapping
pluginu
CoreMappingPlugin
, konkrétně v metodě
createEntity
. V současné podobě podle mě neumí vytvořit
zanořenou entitu ani jinou třídu.
Je tu řešení, kdy se dá ve vaší entitě (v mém případě
User
) implementovat metoda normalize
a podle jména
následně rozlišit, kdy z příchozích dat (pole), vytvořit objekt
(Address
).
Problém je, že je to vázané na jméno (na typ to nejde, tuto informaci v této metodě nemáte) a pokud máte více entit, se kterými chcete v projektu pracovat, tak to musíte udělat všude.
Za mě by bylo asi nejlepší tam nějak dostat
Symfony\Serialiazer
(ideálně přes config jako validator), který
tohle řeší a umí i další věci (DateTime,
CamelCaseToSnakeCase, atd…).
Editoval Richard Faila (16. 8. 2022 9:49)
- Richard Faila
- Člen | 40
Nakonec jsem to vyřešil tak, jak jsem uváděl v https://forum.nette.org/…orene-entity#…
tj. podědil jsem si CoreMappingPlugin
a doplnil do něj podporu
pro Symfony\Serialiazer
, který se potom používá v metodě
createEntity
třídy
Apitte\Core\Mapping\RequestEntityMapping
kterou jsem musel taky
podědit a přepsat.
- kleinpeter
- Člen | 7
Ahoj,
potýkám se se stejným problémem.
Mohl bych tě poprosit o update toho repository? Nenašel jsem tam to dědění
CoreMappingPluginu a implementaci SymfonySerializer.
Díky!
Editoval kleinpeter (15. 10. 2022 20:51)
- mcmatak
- Člen | 504
Richard Faila napsal(a):
Nakonec jsem to vyřešil tak, jak jsem uváděl v https://forum.nette.org/…orene-entity#… tj. podědil jsem si
CoreMappingPlugin
a doplnil do něj podporu proSymfony\Serialiazer
, který se potom používá v metoděcreateEntity
třídyApitte\Core\Mapping\RequestEntityMapping
kterou jsem musel taky podědit a přepsat.
mohu taky poprosit podívat jak si to vyřešil? mám stejný problém
- Felix
- Nette Core | 1245
Pouzit vlastni implementaci IEntityValidatoru by neslo?
V apitte je aktualne predpripraveny SymfonyValidator (https://github.com/…alidator.php)
api:
plugins:
Apitte\Core\DI\Plugin\CoreMappingPlugin:
request:
validator: Apitte\Core\Mapping\Validator\SymfonyValidator()
Bud pouzit ten pripraveny a nebo splnit interface IEntityValidator a naimplementovat si svoje?
- rasstislav
- Člen | 2
@Felix problém nie je vo validácii, to je až výsledok problému (dôsledok), ktorý je spôsobený inde a teda treba odstrániť príčinu a to presne tú, ako píše @RichardFaila, že pri mapovaní sa nevie vytvoriť vnorená entita, teda napríklad user, krorý má role alebo produkt, ktorý má kategóriu a pod.
Ja som si to nejako ohackoval, taktiež s využitím
Symfony/Serializer
, ale by default by to malo fungovať už v core
balíčka.