Skip to content

Commit 3c41f7e

Browse files
authored
Refactor attribute definition request handling (#161)
Summary by CodeRabbit Release Notes Documentation Updated API documentation reference to external hosted documentation. New Features Added support for attribute options in subscriber attribute definitions. Enhanced attribute validation with improved error reporting. Changes Restructured API endpoint paths for subscriber attributes. Removed deprecated table_name field from attribute definitions. Attribute type field now restricted to predefined values. Thanks for contributing to phpList!
1 parent f4c6993 commit 3c41f7e

34 files changed

+530
-456
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ which also has more detailed installation instructions in the README.
3838

3939
## API Documentation
4040

41-
Visit `/docs` endpoint to access the full interactive documentation for `phpList/rest-api`.
41+
Visit `https://phplist.github.io/restapi-docs/` endpoint to access the full interactive documentation for `phpList/rest-api`.
4242

4343
Look at the **"API Documentation with Swagger"** section in the [contribution guide](.github/CONTRIBUTING.md) for more information on API documenation.
4444

config/services/validators.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,8 @@ services:
3636
autowire: true
3737
autoconfigure: true
3838
tags: [ 'validator.constraint_validator' ]
39+
40+
PhpList\Core\Domain\Identity\Validator\AttributeTypeValidator:
41+
autowire: true
42+
autoconfigure: true
43+

src/Common/EventListener/ExceptionListener.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Exception;
88
use PhpList\Core\Domain\Identity\Exception\AdminAttributeCreationException;
9+
use PhpList\Core\Domain\Subscription\Exception\AttributeDefinitionCreationException;
910
use PhpList\Core\Domain\Subscription\Exception\SubscriptionCreationException;
1011
use Symfony\Component\HttpFoundation\JsonResponse;
1112
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
@@ -42,6 +43,11 @@ public function onKernelException(ExceptionEvent $event): void
4243
'message' => $exception->getMessage(),
4344
], $exception->getStatusCode());
4445
$event->setResponse($response);
46+
} elseif ($exception instanceof AttributeDefinitionCreationException) {
47+
$response = new JsonResponse([
48+
'message' => $exception->getMessage(),
49+
], $exception->getStatusCode());
50+
$event->setResponse($response);
4551
} elseif ($exception instanceof ValidatorException) {
4652
$response = new JsonResponse([
4753
'message' => $exception->getMessage(),

src/Identity/Controller/AdminAttributeDefinitionController.php

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
use PhpList\RestBundle\Common\Controller\BaseController;
1313
use PhpList\RestBundle\Common\Service\Provider\PaginatedDataProvider;
1414
use PhpList\RestBundle\Common\Validator\RequestValidator;
15-
use PhpList\RestBundle\Identity\Request\CreateAttributeDefinitionRequest;
16-
use PhpList\RestBundle\Identity\Request\UpdateAttributeDefinitionRequest;
15+
use PhpList\RestBundle\Identity\Request\AdminAttributeDefinitionRequest;
1716
use PhpList\RestBundle\Identity\Serializer\AdminAttributeDefinitionNormalizer;
1817
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
1918
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -51,7 +50,7 @@ public function __construct(
5150
requestBody: new OA\RequestBody(
5251
description: 'Pass parameters to create admin attribute.',
5352
required: true,
54-
content: new OA\JsonContent(ref: '#/components/schemas/CreateAdminAttributeDefinitionRequest')
53+
content: new OA\JsonContent(ref: '#/components/schemas/AdminAttributeDefinitionRequest')
5554
),
5655
tags: ['admin-attributes'],
5756
parameters: [
@@ -87,8 +86,8 @@ public function create(Request $request): JsonResponse
8786
{
8887
$this->requireAuthentication($request);
8988

90-
/** @var CreateAttributeDefinitionRequest $definitionRequest */
91-
$definitionRequest = $this->validator->validate($request, CreateAttributeDefinitionRequest::class);
89+
/** @var AdminAttributeDefinitionRequest $definitionRequest */
90+
$definitionRequest = $this->validator->validate($request, AdminAttributeDefinitionRequest::class);
9291

9392
$attributeDefinition = $this->definitionManager->create($definitionRequest->getDto());
9493
$this->entityManager->flush();
@@ -107,7 +106,7 @@ public function create(Request $request): JsonResponse
107106
requestBody: new OA\RequestBody(
108107
description: 'Pass parameters to update admin attribute.',
109108
required: true,
110-
content: new OA\JsonContent(ref: '#/components/schemas/CreateAdminAttributeDefinitionRequest')
109+
content: new OA\JsonContent(ref: '#/components/schemas/AdminAttributeDefinitionRequest')
111110
),
112111
tags: ['admin-attributes'],
113112
parameters: [
@@ -153,8 +152,8 @@ public function update(
153152
throw $this->createNotFoundException('Attribute definition not found.');
154153
}
155154

156-
/** @var UpdateAttributeDefinitionRequest $definitionRequest */
157-
$definitionRequest = $this->validator->validate($request, UpdateAttributeDefinitionRequest::class);
155+
/** @var AdminAttributeDefinitionRequest $definitionRequest */
156+
$definitionRequest = $this->validator->validate($request, AdminAttributeDefinitionRequest::class);
158157

159158
$attributeDefinition = $this->definitionManager->update(
160159
attributeDefinition: $attributeDefinition,

src/Identity/OpenApi/SwaggerSchemasRequest.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace PhpList\RestBundle\Identity\OpenApi;
66

77
use OpenApi\Attributes as OA;
8+
use PhpList\Core\Domain\Common\Model\AttributeTypeEnum;
89

910
#[OA\Schema(
1011
schema: 'CreateAdministratorRequest',
@@ -96,15 +97,23 @@
9697
type: 'object'
9798
)]
9899
#[OA\Schema(
99-
schema: 'CreateAdminAttributeDefinitionRequest',
100+
schema: 'AdminAttributeDefinitionRequest',
100101
required: ['name'],
101102
properties: [
102103
new OA\Property(property: 'name', type: 'string', format: 'string', example: 'Country'),
103-
new OA\Property(property: 'type', type: 'string', example: 'checkbox'),
104+
new OA\Property(
105+
property: 'type',
106+
type: 'string',
107+
enum: [
108+
AttributeTypeEnum::TextLine,
109+
AttributeTypeEnum::Hidden,
110+
],
111+
example: 'hidden',
112+
nullable: true
113+
),
104114
new OA\Property(property: 'order', type: 'number', example: 12),
105115
new OA\Property(property: 'default_value', type: 'string', example: 'United States'),
106116
new OA\Property(property: 'required', type: 'boolean', example: true),
107-
new OA\Property(property: 'table_name', type: 'string', example: 'list_attributes'),
108117
],
109118
type: 'object'
110119
)]

src/Identity/OpenApi/SwaggerSchemasResponse.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@
2727
properties: [
2828
new OA\Property(property: 'id', type: 'integer', example: 1),
2929
new OA\Property(property: 'name', type: 'string', example: 'Country'),
30-
new OA\Property(property: 'type', type: 'string', example: 'select'),
30+
new OA\Property(property: 'type', type: 'string', example: 'hidden'),
3131
new OA\Property(property: 'list_order', type: 'integer', example: 12),
3232
new OA\Property(property: 'default_value', type: 'string', example: 'United States'),
3333
new OA\Property(property: 'required', type: 'boolean', example: true),
34-
new OA\Property(property: 'table_name', type: 'string', example: 'ukcounties'),
3534
],
3635
type: 'object'
3736
)]
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\RestBundle\Identity\Request;
6+
7+
use PhpList\Core\Domain\Identity\Model\Dto\AdminAttributeDefinitionDto;
8+
use PhpList\Core\Domain\Subscription\Validator\AttributeTypeValidator;
9+
use PhpList\RestBundle\Common\Request\RequestInterface;
10+
use Symfony\Component\Translation\IdentityTranslator;
11+
use Symfony\Component\Validator\Constraints as Assert;
12+
use Symfony\Component\Validator\Context\ExecutionContextInterface;
13+
use Symfony\Component\Validator\Exception\ValidatorException;
14+
15+
#[Assert\Callback('validateType')]
16+
class AdminAttributeDefinitionRequest implements RequestInterface
17+
{
18+
#[Assert\NotBlank]
19+
public string $name;
20+
21+
#[Assert\Choice(choices: ['hidden', 'textline'], message: 'Invalid type. Allowed values: hidden, textline.')]
22+
public ?string $type = null;
23+
24+
public ?int $order = null;
25+
26+
public ?string $defaultValue = null;
27+
28+
public bool $required = false;
29+
30+
public function getDto(): AdminAttributeDefinitionDto
31+
{
32+
return new AdminAttributeDefinitionDto(
33+
name: $this->name,
34+
type: $this->type,
35+
listOrder: $this->order,
36+
defaultValue: $this->defaultValue,
37+
required: $this->required,
38+
);
39+
}
40+
41+
public function validateType(ExecutionContextInterface $context): void
42+
{
43+
if ($this->type === null) {
44+
return;
45+
}
46+
47+
$validator = new AttributeTypeValidator(new IdentityTranslator());
48+
49+
try {
50+
$validator->validate($this->type);
51+
} catch (ValidatorException $e) {
52+
$context->buildViolation($e->getMessage())
53+
->atPath('type')
54+
->addViolation();
55+
}
56+
}
57+
}

src/Identity/Request/CreateAttributeDefinitionRequest.php

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/Identity/Request/UpdateAttributeDefinitionRequest.php

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/Subscription/Controller/SubscriberAttributeDefinitionController.php

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@
77
use Doctrine\ORM\EntityManagerInterface;
88
use OpenApi\Attributes as OA;
99
use PhpList\Core\Domain\Subscription\Model\SubscriberAttributeDefinition;
10+
use PhpList\Core\Domain\Subscription\Repository\SubscriberAttributeDefinitionRepository;
1011
use PhpList\Core\Domain\Subscription\Service\Manager\AttributeDefinitionManager;
1112
use PhpList\Core\Security\Authentication;
1213
use PhpList\RestBundle\Common\Controller\BaseController;
1314
use PhpList\RestBundle\Common\Service\Provider\PaginatedDataProvider;
1415
use PhpList\RestBundle\Common\Validator\RequestValidator;
15-
use PhpList\RestBundle\Subscription\Request\CreateAttributeDefinitionRequest;
16-
use PhpList\RestBundle\Subscription\Request\UpdateAttributeDefinitionRequest;
16+
use PhpList\RestBundle\Subscription\Request\SubscriberAttributeDefinitionRequest;
1717
use PhpList\RestBundle\Subscription\Serializer\AttributeDefinitionNormalizer;
1818
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
1919
use Symfony\Component\HttpFoundation\JsonResponse;
2020
use Symfony\Component\HttpFoundation\Request;
2121
use Symfony\Component\HttpFoundation\Response;
2222
use Symfony\Component\Routing\Attribute\Route;
2323

24-
#[Route('/subscribers/attributes', name: 'subscriber_attribute_definition_')]
24+
#[Route('/attributes', name: 'subscriber_attribute_definition_')]
2525
class SubscriberAttributeDefinitionController extends BaseController
2626
{
2727
private AttributeDefinitionManager $definitionManager;
@@ -44,14 +44,14 @@ public function __construct(
4444

4545
#[Route('', name: 'create', methods: ['POST'])]
4646
#[OA\Post(
47-
path: '/api/v2/subscribers/attributes',
47+
path: '/api/v2/attributes',
4848
description: '🚧 **Status: Beta** – This method is under development. Avoid using in production. ' .
4949
'Returns created subscriber attribute definition.',
5050
summary: 'Create a subscriber attribute definition.',
5151
requestBody: new OA\RequestBody(
5252
description: 'Pass parameters to create subscriber attribute.',
5353
required: true,
54-
content: new OA\JsonContent(ref: '#/components/schemas/CreateSubscriberAttributeDefinitionRequest')
54+
content: new OA\JsonContent(ref: '#/components/schemas/SubscriberAttributeDefinitionRequest')
5555
),
5656
tags: ['subscriber-attributes'],
5757
parameters: [
@@ -74,6 +74,11 @@ public function __construct(
7474
description: 'Failure',
7575
content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse')
7676
),
77+
new OA\Response(
78+
response: 409,
79+
description: 'Failure',
80+
content: new OA\JsonContent(ref: '#/components/schemas/AlreadyExistsResponse')
81+
),
7782
new OA\Response(
7883
response: 422,
7984
description: 'Failure',
@@ -85,8 +90,8 @@ public function create(Request $request): JsonResponse
8590
{
8691
$this->requireAuthentication($request);
8792

88-
/** @var CreateAttributeDefinitionRequest $definitionRequest */
89-
$definitionRequest = $this->validator->validate($request, CreateAttributeDefinitionRequest::class);
93+
/** @var SubscriberAttributeDefinitionRequest $definitionRequest */
94+
$definitionRequest = $this->validator->validate($request, SubscriberAttributeDefinitionRequest::class);
9095

9196
$attributeDefinition = $this->definitionManager->create($definitionRequest->getDto());
9297
$this->entityManager->flush();
@@ -97,14 +102,14 @@ public function create(Request $request): JsonResponse
97102

98103
#[Route('/{definitionId}', name: 'update', requirements: ['definitionId' => '\d+'], methods: ['PUT'])]
99104
#[OA\Put(
100-
path: '/api/v2/subscribers/attributes/{definitionId}',
105+
path: '/api/v2/attributes/{definitionId}',
101106
description: '🚧 **Status: Beta** – This method is under development. Avoid using in production. ' .
102107
'Returns updated subscriber attribute definition.',
103108
summary: 'Update a subscriber attribute definition.',
104109
requestBody: new OA\RequestBody(
105110
description: 'Pass parameters to update subscriber attribute.',
106111
required: true,
107-
content: new OA\JsonContent(ref: '#/components/schemas/CreateSubscriberAttributeDefinitionRequest')
112+
content: new OA\JsonContent(ref: '#/components/schemas/SubscriberAttributeDefinitionRequest')
108113
),
109114
tags: ['subscriber-attributes'],
110115
parameters: [
@@ -150,8 +155,8 @@ public function update(
150155
throw $this->createNotFoundException('Attribute definition not found.');
151156
}
152157

153-
/** @var UpdateAttributeDefinitionRequest $definitionRequest */
154-
$definitionRequest = $this->validator->validate($request, UpdateAttributeDefinitionRequest::class);
158+
/** @var SubscriberAttributeDefinitionRequest $definitionRequest */
159+
$definitionRequest = $this->validator->validate($request, SubscriberAttributeDefinitionRequest::class);
155160

156161
$attributeDefinition = $this->definitionManager->update(
157162
attributeDefinition: $attributeDefinition,
@@ -165,7 +170,7 @@ public function update(
165170

166171
#[Route('/{definitionId}', name: 'delete', requirements: ['definitionId' => '\d+'], methods: ['DELETE'])]
167172
#[OA\Delete(
168-
path: '/api/v2/subscribers/attributes/{definitionId}',
173+
path: '/api/v2/attributes/{definitionId}',
169174
description: '🚧 **Status: Beta** – This method is under development. Avoid using in production. ' .
170175
'Deletes a single subscriber attribute definition.',
171176
summary: 'Deletes an attribute definition.',
@@ -220,7 +225,7 @@ public function delete(
220225

221226
#[Route('', name: 'get_list', methods: ['GET'])]
222227
#[OA\Get(
223-
path: '/api/v2/subscribers/attributes',
228+
path: '/api/v2/attributes',
224229
description: '🚧 **Status: Beta** – This method is under development. Avoid using in production. ' .
225230
'Returns a JSON list of all subscriber attribute definitions.',
226231
summary: 'Gets a list of all subscriber attribute definitions.',
@@ -287,7 +292,7 @@ public function getPaginated(Request $request): JsonResponse
287292

288293
#[Route('/{definitionId}', name: 'get_one', requirements: ['definitionId' => '\d+'], methods: ['GET'])]
289294
#[OA\Get(
290-
path: '/api/v2/subscribers/attributes/{definitionId}',
295+
path: '/api/v2/attributes/{definitionId}',
291296
description: '🚧 **Status: Beta** – This method is under development. Avoid using in production. ' .
292297
'Returns a single attribute with specified ID.',
293298
summary: 'Gets attribute with specified ID.',
@@ -344,6 +349,13 @@ public function getAttributeDefinition(
344349
throw $this->createNotFoundException('Attribute definition not found.');
345350
}
346351

352+
/** @var SubscriberAttributeDefinitionRepository $repo */
353+
$repo = $this->entityManager->getRepository(SubscriberAttributeDefinition::class);
354+
$hydrated = $repo->findOneByName($attributeDefinition->getName());
355+
if ($hydrated instanceof SubscriberAttributeDefinition) {
356+
$attributeDefinition = $hydrated;
357+
}
358+
347359
return $this->json(
348360
$this->normalizer->normalize($attributeDefinition),
349361
Response::HTTP_OK

0 commit comments

Comments
 (0)