Skip to content

Commit b8e5fff

Browse files
committed
feat: create a new term
1 parent 21b12e0 commit b8e5fff

File tree

9 files changed

+249
-3
lines changed

9 files changed

+249
-3
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,16 @@ $result = $repository->createFindByQueryBuilder([], ['sku' => 'ASC'])
357357
$products = $repository->denormalize($result, Product::class . '[]');
358358
```
359359

360+
### Create a new term
361+
362+
Terms are not duplicated if already existing.
363+
364+
```php
365+
// Create a new product category
366+
$repository = $manager->getRepository(Term::class);
367+
$term = $repository->createTermForTaxonomy('Jewelry', 'product_cat');
368+
```
369+
360370
### Add terms to an entity
361371

362372
```php
@@ -429,6 +439,7 @@ $newProduct = $duplicationService->duplicate($product);
429439
* `Option` and `OptionRepository`
430440
* `PostMeta` and `PostMetaRepository`
431441
* `Term` and `TermRepository`
442+
* `TermTaxonomy` and `TermTaxonomyRepository`
432443
* `User` and `UserRepository`
433444
* `Product` and `ProductRepository` (WooCommerce)
434445
* `ShopOrder` and `ShopOrderRepository` (WooCommerce)

src/Bridge/Entity/DynamicPropertiesTrait.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
namespace Williarin\WordpressInterop\Bridge\Entity;
66

77
use ReflectionNamedType;
8+
use Williarin\WordpressInterop\Exception\InvalidArgumentException;
9+
use Williarin\WordpressInterop\Exception\MethodNotFoundException;
810

911
trait DynamicPropertiesTrait
1012
{
@@ -18,7 +20,7 @@ public function __set(string $property, mixed $value): void
1820
$reflectionType = (new \ReflectionProperty(static::class, $property))->getType();
1921

2022
$expectedType = $reflectionType instanceof ReflectionNamedType
21-
? $reflectionType?->getName()
23+
? $reflectionType->getName()
2224
: (string) $reflectionType;
2325

2426
settype($value, $expectedType);
@@ -32,4 +34,30 @@ public function __get(string $property): mixed
3234
{
3335
return $this->{$property};
3436
}
37+
38+
public function __isset($name): bool
39+
{
40+
return property_exists($this, $name);
41+
}
42+
43+
public function __call(string $name, array $arguments): mixed
44+
{
45+
$propertyName = lcfirst(substr($name, 3));
46+
47+
if (preg_match('/^get[A-Z]/', $name)) {
48+
return $this->__get($propertyName);
49+
}
50+
51+
if (preg_match('/^set[A-Z]/', $name)) {
52+
if (count($arguments) !== 1) {
53+
throw new InvalidArgumentException('Setter needs a value as argument.');
54+
}
55+
56+
$this->__set($propertyName, $arguments[0]);
57+
58+
return $this;
59+
}
60+
61+
throw new MethodNotFoundException(static::class, $name);
62+
}
3563
}

src/Bridge/Entity/Term.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,16 @@ class Term
3535

3636
#[External]
3737
public ?int $count = null;
38+
39+
public function setName(?string $name): self
40+
{
41+
$this->name = $name;
42+
return $this;
43+
}
44+
45+
public function setSlug(?string $slug): self
46+
{
47+
$this->slug = $slug;
48+
return $this;
49+
}
3850
}

src/Bridge/Entity/TermTaxonomy.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Williarin\WordpressInterop\Bridge\Entity;
6+
7+
use AllowDynamicProperties;
8+
use Symfony\Component\Serializer\Annotation\Groups;
9+
use Williarin\WordpressInterop\Attributes\Id;
10+
use Williarin\WordpressInterop\Attributes\RepositoryClass;
11+
use Williarin\WordpressInterop\Bridge\Repository\TermTaxonomyRepository;
12+
13+
#[AllowDynamicProperties]
14+
#[RepositoryClass(TermTaxonomyRepository::class)]
15+
class TermTaxonomy
16+
{
17+
#[Id]
18+
#[Groups('base')]
19+
public ?int $termTaxonomyId = null;
20+
21+
#[Groups('base')]
22+
public ?int $termId = null;
23+
24+
#[Groups('base')]
25+
public ?string $taxonomy = null;
26+
27+
#[Groups('base')]
28+
public ?string $description = null;
29+
30+
#[Groups('base')]
31+
public int $parent = 0;
32+
33+
#[Groups('base')]
34+
public int $count = 0;
35+
36+
public function setTermId(?int $termId): self
37+
{
38+
$this->termId = $termId;
39+
return $this;
40+
}
41+
42+
public function setTaxonomy(?string $taxonomy): self
43+
{
44+
$this->taxonomy = $taxonomy;
45+
return $this;
46+
}
47+
48+
public function setDescription(?string $description): self
49+
{
50+
$this->description = $description;
51+
return $this;
52+
}
53+
54+
public function setParent(int $parent): self
55+
{
56+
$this->parent = $parent;
57+
return $this;
58+
}
59+
60+
public function setCount(int $count): self
61+
{
62+
$this->count = $count;
63+
return $this;
64+
}
65+
}

src/Bridge/Repository/AbstractEntityRepository.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,9 @@ public function persist(mixed $entity): void
189189

190190
unset($baseFields[static::TABLE_IDENTIFIER]);
191191

192-
if ($entity->{static::TABLE_IDENTIFIER} === null) {
192+
$identifierProperty = field_to_property(static::TABLE_IDENTIFIER);
193+
194+
if ($entity->{$identifierProperty} === null) {
193195
$this->entityManager->getConnection()
194196
->createQueryBuilder()
195197
->insert($this->entityManager->getTablesPrefix() . static::TABLE_NAME)
@@ -198,7 +200,8 @@ public function persist(mixed $entity): void
198200
->executeStatement()
199201
;
200202

201-
$entity->{static::TABLE_IDENTIFIER} = (int) $this->entityManager->getConnection()->lastInsertId();
203+
$entity->{$identifierProperty} = (int) $this->entityManager->getConnection()
204+
->lastInsertId();
202205
} else {
203206
$queryBuilder = $this->entityManager->getConnection()
204207
->createQueryBuilder()

src/Bridge/Repository/TermRepository.php

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

77
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
88
use Doctrine\DBAL\Query\QueryBuilder;
9+
use Symfony\Component\String\Slugger\AsciiSlugger;
910
use Williarin\WordpressInterop\Bridge\Entity\BaseEntity;
1011
use Williarin\WordpressInterop\Bridge\Entity\Term;
12+
use Williarin\WordpressInterop\Bridge\Entity\TermTaxonomy;
1113
use Williarin\WordpressInterop\Criteria\SelectColumns;
14+
use Williarin\WordpressInterop\Exception\EntityNotFoundException;
1215

1316
class TermRepository extends AbstractEntityRepository
1417
{
@@ -56,6 +59,42 @@ public function createFindByQueryBuilder(array $criteria, ?array $orderBy): Quer
5659
return $queryBuilder;
5760
}
5861

62+
public function createTermForTaxonomy(string $termName, string $taxonomy): Term
63+
{
64+
try {
65+
$term = $this->findOneBy([
66+
'name' => $termName,
67+
'taxonomy' => $taxonomy
68+
]);
69+
} catch (EntityNotFoundException) {
70+
$term = (new Term())
71+
->setName($termName)
72+
->setSlug((new AsciiSlugger())->slug($termName)->lower()->toString())
73+
;
74+
75+
$this->persist($term);
76+
77+
$termTaxonomy = (new TermTaxonomy())
78+
->setTermId($term->termId)
79+
->setTaxonomy($taxonomy)
80+
;
81+
82+
$this->entityManager->getRepository(TermTaxonomy::class)
83+
->persist($termTaxonomy)
84+
;
85+
86+
$term = $this->findOneBy([
87+
'name' => $termName,
88+
'taxonomy' => $taxonomy
89+
]);
90+
}
91+
92+
return $term;
93+
}
94+
95+
/**
96+
* @param Term[] $terms
97+
*/
5998
public function addTermsToEntity(BaseEntity $entity, array $terms): void
6099
{
61100
foreach ($terms as $term) {
@@ -82,6 +121,9 @@ public function addTermsToEntity(BaseEntity $entity, array $terms): void
82121
$this->recountTerms();
83122
}
84123

124+
/**
125+
* @param Term[] $terms
126+
*/
85127
public function removeTermsFromEntity(BaseEntity $entity, array $terms): void
86128
{
87129
foreach ($terms as $term) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Williarin\WordpressInterop\Bridge\Repository;
6+
7+
use Williarin\WordpressInterop\Bridge\Entity\TermTaxonomy;
8+
9+
class TermTaxonomyRepository extends AbstractEntityRepository
10+
{
11+
protected const TABLE_NAME = 'term_taxonomy';
12+
protected const TABLE_IDENTIFIER = 'term_taxonomy_id';
13+
protected const FALLBACK_ENTITY = TermTaxonomy::class;
14+
15+
public function __construct()
16+
{
17+
parent::__construct(TermTaxonomy::class);
18+
}
19+
}

test/Test/Bridge/Repository/PostRepositoryTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Williarin\WordpressInterop\Bridge\Repository\EntityRepositoryInterface;
1010
use Williarin\WordpressInterop\Criteria\Operand;
1111
use Williarin\WordpressInterop\Exception\EntityNotFoundException;
12+
use Williarin\WordpressInterop\Exception\InvalidArgumentException;
1213
use Williarin\WordpressInterop\Exception\InvalidEntityException;
1314
use Williarin\WordpressInterop\Exception\InvalidFieldNameException;
1415
use Williarin\WordpressInterop\Exception\InvalidOrderByOrientationException;
@@ -267,4 +268,36 @@ public function testPersistExistingPost(): void
267268
self::assertSame('Another post with a new title', $post->postTitle);
268269
self::assertSame('publish', $post->postStatus);
269270
}
271+
272+
public function testDynamicSetter(): void
273+
{
274+
$post = new Post();
275+
276+
$post = $post->setDynamicProperty('Hello');
277+
self::assertSame('Hello', $post->dynamicProperty);
278+
}
279+
280+
public function testDynamicSetterNoArgument(): void
281+
{
282+
$post = new Post();
283+
284+
$this->expectException(InvalidArgumentException::class);
285+
$post->setDynamicProperty();
286+
}
287+
288+
public function testWrongMethodCall(): void
289+
{
290+
$post = new Post();
291+
292+
$this->expectException(MethodNotFoundException::class);
293+
$post->nonExistentMethod();
294+
}
295+
296+
public function testDynamicGetter(): void
297+
{
298+
$post = new Post();
299+
$post->postContent = 'Hello';
300+
301+
self::assertSame('Hello', $post->getPostContent());
302+
}
270303
}

test/Test/Bridge/Repository/TermRepositoryTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Williarin\WordpressInterop\Criteria\SelectColumns;
1515
use Williarin\WordpressInterop\Test\TestCase;
1616

17+
use function Williarin\WordpressInterop\Util\String\field_to_property;
18+
1719
class TermRepositoryTest extends TestCase
1820
{
1921
/** @var TermRepository */
@@ -99,6 +101,27 @@ public function testFindByPostRelationshipCondition(): void
99101
], array_combine(array_column($terms, 'taxonomy'), array_column($terms, 'name')));
100102
}
101103

104+
public function testCreateNewTermForTaxonomy(): void
105+
{
106+
$term = $this->repository->createTermForTaxonomy('Jewelry', 'product_cat');
107+
108+
$this->validateTerm($term, [
109+
'name' => 'Jewelry',
110+
'slug' => 'jewelry',
111+
'taxonomy' => 'product_cat',
112+
'count' => 0,
113+
]);
114+
}
115+
116+
public function testCreateTermForTaxonomyNoDuplicate(): void
117+
{
118+
$term1 = $this->repository->createTermForTaxonomy('Jewelry', 'product_cat');
119+
$term2 = $this->repository->createTermForTaxonomy('Jewelry', 'product_cat');
120+
121+
self::assertEquals($term1, $term2);
122+
self::assertNotSame($term1, $term2);
123+
}
124+
102125
public function testAddTermsToEntity(): void
103126
{
104127
$hoodieTerms = $this->repository->findBy([
@@ -295,4 +318,14 @@ static function (Term $term) {
295318

296319
self::assertEquals(['simple', 'Hoodies', 'MegaBrand'], array_column($terms, 'name'));
297320
}
321+
322+
private function validateTerm(Term $term, array $values): void
323+
{
324+
foreach ($values as $key => $value) {
325+
self::assertEquals($value, $term->{field_to_property($key)});
326+
}
327+
328+
self::assertIsInt($term->termId);
329+
self::assertIsInt($term->termTaxonomyId);
330+
}
298331
}

0 commit comments

Comments
 (0)