Skip to content

Commit 254cde7

Browse files
committed
perf: runtime performance optmization
1 parent f7273b4 commit 254cde7

5 files changed

Lines changed: 240 additions & 84 deletions

File tree

.githooks/pre-commit.sh

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,14 @@ fi
188188
printf "${YELLOW}Pest Unit Tests${NC}\n"
189189
if $HAS_PEST; then
190190
if RUN $PEST --testdox --colors=always; then
191-
# All good, also re-generate clover.xml
192-
$PEST --coverage --coverage-clover clover.xml
193-
git add clover.xml
194-
printf "${GREEN}Pest tests passed${NC}\n"
191+
# All good, also re-generate clover.xml and stage it
192+
if RUN env XDEBUG_MODE=coverage $PEST --coverage --coverage-clover clover.xml; then
193+
git add clover.xml
194+
printf "${GREEN}Pest tests passed; clover.xml updated${NC}\n"
195+
else
196+
printf "${RED}Pest coverage generation failed${NC}\n"
197+
PASS=false
198+
fi
195199
else
196200
PASS=false
197201
fi

README.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,45 @@ echo $twig->render('index.twig', [
170170
### XSLT
171171
You can use the XML data to transform it into HTML using XSLT.
172172
```php
173-
$xml = (new DOM\ORM\Storage\StorageService())->read();
173+
$xml = DOM\ORM\Storage\StorageService::fromConfig()->read();
174174
$dom = (new DOMDocument())->loadXML($xml);
175175
$xslt = (new XSLTProcessor())->importStylesheet(DOMDocument::load('path/to/stylesheet.xsl'));
176176

177177
echo $xslt->transformToXML($dom);
178178
```
179179

180+
## Testing And Coverage
181+
182+
Run the full Pest test suite:
183+
184+
```bash
185+
./vendor/bin/pest
186+
```
187+
188+
Run Pest with a human-friendly output format:
189+
190+
```bash
191+
./vendor/bin/pest --testdox
192+
```
193+
194+
Generate an HTML coverage report (output directory: `build/coverage-html`):
195+
196+
```bash
197+
XDEBUG_MODE=coverage ./vendor/bin/pest --coverage-html build/coverage-html
198+
```
199+
200+
Update `clover.xml` (used by CI and quality tooling):
201+
202+
```bash
203+
XDEBUG_MODE=coverage ./vendor/bin/pest --coverage-clover clover.xml
204+
```
205+
206+
Generate both HTML coverage and a fresh `clover.xml` in one run:
207+
208+
```bash
209+
XDEBUG_MODE=coverage ./vendor/bin/pest --coverage-html build/coverage-html --coverage-clover clover.xml
210+
```
211+
180212
### Roadmap
181213

182214
- [ ] Add support for Many-to-many relationships using hash maps.

src/Repository/AbstractEntityRepository.php

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ abstract class AbstractEntityRepository implements EntityRepositoryInterface
1111
use EntityManagerTrait;
1212

1313
private string $entityType;
14+
private string $entityTypeXPathLiteral;
1415
private string $entityClass;
1516

1617
public function __construct(string $entityType)
1718
{
1819
$this->entityType = $entityType;
20+
$this->entityTypeXPathLiteral = $this->toXPathValue($entityType);
1921
$this->entityClass = $this->getEntityByEntityType($entityType);
2022
$this->init();
2123
}
@@ -30,7 +32,11 @@ public function getEntityType(): string
3032
*/
3133
public function findAll(): ?Collection
3234
{
33-
$nodes = $this->xpath->query(\sprintf('//item[@type="%s"]', $this->entityType));
35+
$nodes = $this->queryNodes(\sprintf('//item[@type=%s]', $this->entityTypeXPathLiteral));
36+
if ($nodes === null) {
37+
return null;
38+
}
39+
3440
if ($nodes->length < 1) {
3541
return null;
3642
}
@@ -42,16 +48,24 @@ public function findAll(): ?Collection
4248

4349
public function find(string $id): ?EntityInterface
4450
{
45-
$node = $this->xpath->query(\sprintf('//item[@type="%s" and @id="%s"]', $this->entityType, $id));
46-
if ($node->length > 1) {
51+
$nodes = $this->queryNodes(\sprintf(
52+
'//item[@type=%s and @id=%s]',
53+
$this->entityTypeXPathLiteral,
54+
$this->toXPathValue($id)
55+
));
56+
if ($nodes === null) {
57+
return null;
58+
}
59+
60+
if ($nodes->length > 1) {
4761
throw new \Exception('Multiple entities found with the same ID.');
4862
}
4963

50-
if ($node->length < 1) {
64+
if ($nodes->length < 1) {
5165
return null;
5266
}
5367

54-
$array = $this->serializer->decode($node, SchemaEncoder::FORMAT);
68+
$array = $this->serializer->decode($nodes, SchemaEncoder::FORMAT);
5569

5670
return $this->serializer->denormalize($array, $this->entityClass);
5771
}
@@ -72,18 +86,31 @@ public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = nu
7286
*/
7387
public function findOneBy(array $criteria, ?array $orderBy = null): ?EntityInterface
7488
{
75-
$additionalArgs = '';
89+
if (isset($criteria['id']) && \count($criteria) === 1) {
90+
return $this->find((string)$criteria['id']);
91+
}
92+
93+
$predicates = [\sprintf('@type=%s', $this->entityTypeXPathLiteral)];
7694
if (isset($criteria['id'])) {
77-
$additionalArgs .= \sprintf(' and @id="%s" ', $criteria['id']);
95+
$predicates[] = \sprintf('@id=%s', $this->toXPathValue((string)$criteria['id']));
7896
unset($criteria['id']);
7997
}
8098

8199
foreach ($criteria as $key => $value) {
82-
$additionalArgs .= \sprintf(' and ./fragment[@name="%s"] = "%s"', \trim($key), \trim((string)$value));
100+
$predicates[] = \sprintf(
101+
'./fragment[@name=%s]=%s',
102+
$this->toXPathValue($key),
103+
$this->toXPathValue((string)$value)
104+
);
105+
}
106+
107+
$query = '//item[' . \implode(' and ', $predicates) . ']';
108+
$nodes = $this->queryNodes($query);
109+
if ($nodes === null) {
110+
return null;
83111
}
84112

85-
$query = \sprintf('//item[@type="%s" %s]', $this->entityType, $additionalArgs);
86-
$node = $this->xpath->query($query)->item(0);
113+
$node = $nodes->item(0);
87114
if ($node === null) {
88115
return null;
89116
}
@@ -97,4 +124,39 @@ public function remove(string $id): void
97124
{
98125
$this->removeById($id);
99126
}
127+
128+
/**
129+
* @return \DOMNodeList<\DOMNode>|null
130+
*/
131+
private function queryNodes(string $query): ?\DOMNodeList
132+
{
133+
$nodes = $this->xpath->query($query);
134+
135+
return ($nodes === false) ? null : $nodes;
136+
}
137+
138+
private function xpathLiteral(string $value): string
139+
{
140+
if (!\str_contains($value, "'")) {
141+
return "'{$value}'";
142+
}
143+
144+
if (!\str_contains($value, '"')) {
145+
return '"' . $value . '"';
146+
}
147+
148+
$parts = \explode("'", $value);
149+
$quotedParts = \array_map(static fn (string $part): string => "'{$part}'", $parts);
150+
151+
return 'concat(' . \implode(', "\'", ', $quotedParts) . ')';
152+
}
153+
154+
private function toXPathValue(string $value): string
155+
{
156+
if (!\str_contains($value, "'") && !\str_contains($value, '"')) {
157+
return '"' . $value . '"';
158+
}
159+
160+
return $this->xpathLiteral($value);
161+
}
100162
}

0 commit comments

Comments
 (0)