Skip to content

Commit 0db23f7

Browse files
committed
fix duplication of data when providing parentwikidataid
1 parent 5e7eed0 commit 0db23f7

File tree

10 files changed

+506
-31
lines changed

10 files changed

+506
-31
lines changed

config/packages/security.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ security:
2929
# Easy way to control access for large sections of your site
3030
# Note: Only the *first* access control that matches will be used
3131
access_control:
32+
- { host: '%host_api%', path: ^/system/healthcheck, roles: PUBLIC_ACCESS, ips: ['127.0.0.1'] }
3233
# - { path: ^/admin, roles: ROLE_ADMIN }
3334
# - { path: ^/, roles: ROLE_USER }
3435

src/Field/Application/FieldService.php

+56-18
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use App\Field\Domain\Enum\FieldPlace;
99
use App\Field\Domain\Exception\FieldEntityNotFoundException;
1010
use App\Field\Domain\Exception\FieldInvalidNameException;
11+
use App\Field\Domain\Exception\FieldParentWikidataIdNotFoundException;
1112
use App\Field\Domain\Exception\FieldUnicityViolationException;
1213
use App\Field\Domain\Model\Field;
1314
use App\Field\Domain\Repository\FieldRepositoryInterface;
@@ -48,15 +49,13 @@ public function upsertFields(Place|Community $entity, array $fieldPayloads): voi
4849
Community::class => FieldCommunity::tryFrom($fieldPayload->name),
4950
default => null,
5051
};
51-
$aliases = match ($entity::class) {
52-
Place::class => FieldPlace::ALIASES,
53-
Community::class => FieldCommunity::ALIASES,
54-
default => null,
55-
};
52+
5653
if (null === $enumValue) {
5754
throw new FieldInvalidNameException($fieldPayload->name);
5855
}
5956

57+
$this->maybeTransformAlias($entity, $enumValue, $fieldPayload);
58+
6059
$field = $this->getOrCreate(
6160
$entity,
6261
$enumValue,
@@ -69,9 +68,6 @@ public function upsertFields(Place|Community $entity, array $fieldPayloads): voi
6968
$field->place = $entity;
7069
}
7170

72-
if (in_array($enumValue->name, array_keys($aliases))) {
73-
$fieldPayload->name = $aliases[$enumValue->name]->value;
74-
}
7571
$field->name = $fieldPayload->name;
7672
$field->value = $value;
7773
$field->engine = $fieldPayload->engine;
@@ -83,8 +79,7 @@ public function upsertFields(Place|Community $entity, array $fieldPayloads): voi
8379
// Unique constraints validation (TODO use custom Assert instead)
8480
if (null !== $field->value
8581
&& in_array($field->name, Field::UNIQUE_CONSTRAINTS, true)
86-
&& (null !== $attachedToId = $this->fieldRepo->exists($entity->id, $enumValue, $field->value))
87-
&& $attachedToId !== $entity->id->toString()
82+
&& $this->fieldRepo->existOusideOf($entity->id, $enumValue, $field->value)
8883
) {
8984
throw new FieldUnicityViolationException($field->name, $field->value);
9085
}
@@ -163,14 +158,9 @@ private function maybeTransformEntities(FieldCommunity|FieldPlace $nameEnum, mix
163158
return $instances->toArray();
164159
} else {
165160
$instance = null;
166-
if ($nameEnum->name === FieldCommunity::PARENT_WIKIDATA_ID->name) {
167-
assert(is_int($value));
168-
$instance = $repo->withWikidataId($value)->asCollection()[0];
169-
} else {
170-
// That's an object
171-
assert(is_string($value));
172-
$instance = $repo->ofId(Uuid::fromString($value));
173-
}
161+
// That's an object
162+
assert(is_string($value));
163+
$instance = $repo->ofId(Uuid::fromString($value));
174164

175165
if (!$instance) {
176166
throw new FieldEntityNotFoundException($value);
@@ -179,4 +169,52 @@ private function maybeTransformEntities(FieldCommunity|FieldPlace $nameEnum, mix
179169
return $instance;
180170
}
181171
}
172+
173+
private function maybeTransformAlias(Place|Community $entity, FieldCommunity|FieldPlace &$enumValue, Field &$fieldPayload): void
174+
{
175+
$aliases = match ($entity::class) {
176+
Place::class => FieldPlace::ALIASES,
177+
Community::class => FieldCommunity::ALIASES,
178+
default => null,
179+
};
180+
181+
if (!in_array($enumValue->name, array_keys($aliases))) {
182+
return;
183+
}
184+
185+
$enumValue = $aliases[$enumValue->name];
186+
$fieldPayload->value = match ($fieldPayload->name) {
187+
FieldCommunity::PARENT_WIKIDATA_ID->value => $this->wikidataIdToCommunityId($fieldPayload->value),
188+
FieldPlace::PARENT_WIKIDATA_IDS->value => $this->wikidataIdsToCommunityIds($fieldPayload->value),
189+
default => null,
190+
};
191+
$fieldPayload->name = $enumValue->value;
192+
}
193+
194+
private function wikidataIdToCommunityId(int $wikidataId): string
195+
{
196+
$fields = $this->fieldRepo->getNameValueFields(FieldCommunity::WIKIDATA_ID, $wikidataId);
197+
if (0 === count($fields)) {
198+
throw new FieldParentWikidataIdNotFoundException([$wikidataId]);
199+
}
200+
201+
return $fields[0]->community->id->toString() ?? $fields[0]->place->id->toString();
202+
}
203+
204+
/**
205+
* @param int[] $wikidataIds
206+
*
207+
* @return string[]
208+
*/
209+
private function wikidataIdsToCommunityIds(array $wikidataIds): array
210+
{
211+
$fields = $this->fieldRepo->getNameValueFields(FieldCommunity::WIKIDATA_ID, $wikidataIds);
212+
$foundWikidataIds = array_map(fn (Field $field) => $field->getValue(), $fields);
213+
$missingWikidataIds = array_diff($wikidataIds, $foundWikidataIds);
214+
if (count($fields) !== count($wikidataIds)) {
215+
throw new FieldParentWikidataIdNotFoundException($missingWikidataIds);
216+
}
217+
218+
return array_map(fn (Field $field) => $field->community->id->toString() ?? $field->place->id->toString(), $fields);
219+
}
182220
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Field\Domain\Exception;
6+
7+
use ApiPlatform\Metadata\ErrorResource;
8+
use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;
9+
use Symfony\Component\Serializer\Annotation\Groups;
10+
11+
#[ErrorResource]
12+
class FieldParentWikidataIdNotFoundException extends \Exception implements ProblemExceptionInterface
13+
{
14+
/**
15+
* @param int[] $parentWikidataIds
16+
*/
17+
public function __construct(
18+
private readonly array $parentWikidataIds,
19+
) {
20+
}
21+
22+
#[Groups(['communities', 'places'])]
23+
public function getType(): string
24+
{
25+
return 'FieldParentWikidataIdNotFoundException';
26+
}
27+
28+
#[Groups(['communities', 'places'])]
29+
public function getTitle(): ?string
30+
{
31+
return 'parentWikidataId not found';
32+
}
33+
34+
#[Groups(['communities', 'places'])]
35+
public function getStatus(): ?int
36+
{
37+
return 404;
38+
}
39+
40+
#[Groups(['communities', 'places'])]
41+
public function getDetail(): ?string
42+
{
43+
return sprintf('Field parentWikidataId %s not found', implode(', ', $this->parentWikidataIds));
44+
}
45+
46+
/**
47+
* @codeCoverageIgnore
48+
*/
49+
public function getInstance(): ?string
50+
{
51+
return null;
52+
}
53+
}

src/Field/Domain/Model/Field.php

-4
Original file line numberDiff line numberDiff line change
@@ -78,25 +78,21 @@ class Field
7878
public ?\DateTimeImmutable $dateVal = null;
7979

8080
#[ORM\ManyToOne(targetEntity: Community::class, inversedBy: 'fieldsAsCommunityVal')]
81-
#[Groups(['communities'])]
8281
public ?Community $communityVal = null;
8382

8483
/**
8584
* @var Collection<int, Community>
8685
*/
8786
#[ORM\ManyToMany(targetEntity: Community::class, inversedBy: 'fieldsAsCommunitiesVal')]
88-
#[Groups(['communities'])]
8987
public Collection $communitiesVal;
9088

9189
#[ORM\ManyToOne(targetEntity: Place::class, inversedBy: 'fieldsAsPlaceVal')]
92-
#[Groups(['places'])]
9390
public ?Place $placeVal = null;
9491

9592
/**
9693
* @var Collection<int, Place>
9794
*/
9895
#[ORM\ManyToMany(targetEntity: Place::class, inversedBy: 'fieldsAsPlacesVal')]
99-
#[Groups(['places'])]
10096
public Collection $placesVal;
10197

10298
#[ORM\ManyToOne(targetEntity: Agent::class, inversedBy: 'fields')]

src/Field/Domain/Repository/FieldRepositoryInterface.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,10 @@ interface FieldRepositoryInterface extends RepositoryInterface
1717
{
1818
public function add(Field $field): void;
1919

20-
public function exists(Uuid $id, FieldPlace|FieldCommunity $fieldName, mixed $fieldValue): ?string;
20+
/**
21+
* @return Field[]
22+
*/
23+
public function getNameValueFields(FieldPlace|FieldCommunity $fieldName, mixed $fieldValue): array;
24+
25+
public function existOusideOf(Uuid $id, FieldPlace|FieldCommunity $fieldName, mixed $fieldValue): bool;
2126
}

src/Field/Infrastructure/Doctrine/DoctrineFieldListener.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ private function onFieldParentCommunityChange(Field $field): void
9292
$parent = $this->communityRepo->addSelectField()->ofId($field->getValue()->id);
9393
$parentTypeField = $parent->getMostTrustableFieldByName(FieldCommunity::TYPE);
9494
if ($parentTypeField->getValue() === CommunityType::DIOCESE->value) {
95-
$dioceseName = $parent->getMostTrustableFieldByName(FieldCommunity::NAME)->getValue();
95+
$dioceseName = $parent->getMostTrustableFieldByName(FieldCommunity::NAME)?->getValue();
9696
$this->searchHelper->upsertElement(
9797
SearchIndex::PARISH,
9898
$community->id->toString(),

src/Field/Infrastructure/Doctrine/DoctrineFieldRepository.php

+25-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use App\Shared\Infrastructure\Doctrine\DoctrineRepository;
1010
use Doctrine\ORM\EntityManagerInterface;
1111
use Doctrine\ORM\Query\Expr\Comparison;
12+
use Doctrine\ORM\Query\Expr\Func;
1213
use Doctrine\ORM\QueryBuilder;
1314
use Symfony\Component\Uid\Uuid;
1415

@@ -31,11 +32,27 @@ public function add(Field $field): void
3132
}
3233

3334
/**
34-
* Checks if the specific field exists across the database. If it is, will return the UUID.
35+
* @return Field[]
3536
*/
36-
public function exists(Uuid $id, FieldPlace|FieldCommunity $fieldName, mixed $fieldValue): ?string
37+
public function getNameValueFields(FieldPlace|FieldCommunity $fieldName, mixed $fieldValue): array
3738
{
3839
$qb = $this->query();
40+
41+
return $qb->where($this->whereFieldEquals(
42+
$qb,
43+
$fieldName,
44+
$fieldValue,
45+
))
46+
->andWhere($qb->expr()->eq('field.name', ':fieldName'))
47+
->setParameter('fieldName', $fieldName)
48+
->getQuery()
49+
->getResult();
50+
}
51+
52+
public function existOusideOf(Uuid $id, FieldPlace|FieldCommunity $fieldName, mixed $fieldValue): bool
53+
{
54+
$qb = $this->query();
55+
3956
$row = $qb->select('COALESCE(IDENTITY(field.community), IDENTITY(field.place)) as attachedToId')
4057
->where($this->whereFieldEquals(
4158
$qb,
@@ -52,16 +69,20 @@ public function exists(Uuid $id, FieldPlace|FieldCommunity $fieldName, mixed $fi
5269
->getQuery()
5370
->getOneOrNullResult();
5471

55-
return $row['attachedToId'] ?? null;
72+
return isset($row['attachedToId']);
5673
}
5774

58-
private function whereFieldEquals(QueryBuilder $qb, FieldPlace|FieldCommunity $fieldName, mixed $fieldValue, string $alias = 'field'): Comparison
75+
private function whereFieldEquals(QueryBuilder $qb, FieldPlace|FieldCommunity $fieldName, mixed $fieldValue, string $alias = 'field'): Comparison|Func
5976
{
6077
$propertyName = Field::getPropertyName($fieldName);
6178
$parameterName = "{$alias}_value";
6279

6380
$qb->setParameter($parameterName, $fieldValue);
6481

82+
if (is_array($fieldValue)) {
83+
return $qb->expr()->in("$alias.$propertyName", ":$parameterName");
84+
}
85+
6586
return $qb->expr()->eq("$alias.$propertyName", ":$parameterName");
6687
}
6788
}

src/FieldHolder/Community/Domain/Model/Community.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ class Community extends FieldHolder
2525
#[ORM\Column(type: 'uuid', unique: true)]
2626
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
2727
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
28-
#[Groups(['communities'])]
2928
public ?Uuid $id = null;
3029

3130
/**
3231
* @var Collection<int, Field>
3332
*/
3433
#[ORM\OneToMany(targetEntity: Field::class, mappedBy: 'community')]
34+
#[Groups(['communities'])]
3535
public Collection $fields;
3636

3737
/**

0 commit comments

Comments
 (0)