Skip to content

Commit 4ca347a

Browse files
committed
WiP
1 parent 62a6374 commit 4ca347a

31 files changed

+1382
-54
lines changed

composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name" : "templado/engine",
3-
"description" : "A pragmatic approach to templating for PHP 7.2+",
3+
"description" : "A pragmatic approach to templating for PHP 8.2+",
44
"license" : "BSD-3-Clause",
55
"authors" : [
66
{
@@ -13,7 +13,7 @@
1313
"issues" : "https://github.com/templado/engine/issues"
1414
},
1515
"require" : {
16-
"php" : "^7.2|^8.0",
16+
"php" : "^8.2",
1717
"ext-dom" : "*",
1818
"ext-libxml": "*",
1919
"theseer/css2xpath": "^2.0"

src/Engine.php

-28
This file was deleted.

src/FileReader.php

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php declare(strict_types = 1);
2+
namespace Templado\Engine;
3+
4+
interface FileReader {
5+
public function fromFile(Filename $filename, ?Id $id = null): Templado;
6+
}

src/FileWriter.php

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php declare(strict_types = 1);
2+
namespace Templado\Engine;
3+
4+
interface FileWriter {
5+
public function toFile(Templado $document, Filename $filename): void;
6+
}

src/Html.php

-24
This file was deleted.

src/Id.php

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php declare(strict_types = 1);
2+
namespace Templado\Engine;
3+
4+
use InvalidArgumentException;
5+
6+
readonly class Id {
7+
8+
private string $id;
9+
10+
public function __construct(string $id) {
11+
$this->ensureNotEmpty($id);
12+
$this->ensureFollowsHtml5Rules($id);
13+
$this->id = $id;
14+
}
15+
16+
public function asString(): string {
17+
return $this->id;
18+
}
19+
20+
private function ensureNotEmpty(string $id): void {
21+
if ($id === '') {
22+
throw new InvalidArgumentException('ID must not be empty');
23+
}
24+
}
25+
26+
private function ensureFollowsHtml5Rules(string $id): void {
27+
// https://www.w3.org/TR/html5-author/global-attributes.html#the-id-attribute
28+
// https://www.w3.org/TR/2012/WD-html5-20121025/single-page.html#space-character
29+
$invalid = "\u{0020}". // SPACE
30+
"\u{0009}". // TAB
31+
"\u{000A}". // LF
32+
"\u{000C}". //"FF"
33+
"\u{000D}"; // "CR"
34+
35+
if (preg_match('/*[' . $invalid . ']/', $id)) {
36+
throw new InvalidArgumentException(
37+
'ID must not contain space type charectars (https://www.w3.org/TR/html5-author/global-attributes.html#the-id-attribute)'
38+
);
39+
}
40+
}
41+
42+
}
File renamed without changes.

src/Selector.php

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php declare(strict_types = 1);
2+
namespace Templado\Engine;
3+
4+
use DOMNode;
5+
6+
interface Selector {
7+
public function select(DOMNode $context): Selection;
8+
}

src/Serializer.php

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php declare(strict_types = 1);
2+
namespace Templado\Engine;
3+
4+
interface Serializer {
5+
public function serialize(Templado $document): string;
6+
}

src/Templado.php

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php declare(strict_types = 1);
2+
namespace Templado\Engine;
3+
4+
use DOMDocument;
5+
6+
final readonly class Templado {
7+
8+
private function __construct(
9+
private DOMDocument $dom,
10+
private ?Id $id
11+
) {}
12+
13+
public static function fromString(string $markup, ?Id $id = null): self {
14+
\libxml_use_internal_errors(true);
15+
\libxml_clear_errors();
16+
17+
$dom = new DOMDocument();
18+
$dom->preserveWhiteSpace = false;
19+
$tmp = $dom->loadXML($markup);
20+
21+
if (!$tmp || \libxml_get_last_error()) {
22+
$errors = \libxml_get_errors();
23+
\libxml_clear_errors();
24+
25+
throw new TempladoParsingException(...$errors);
26+
}
27+
28+
}
29+
30+
public static function fromDomDocument(DOMDocument $dom, ?Id $id = null): self {
31+
}
32+
33+
public function extract(Selector $selector, ?Id $id = null): self {
34+
}
35+
36+
public function asString(?Serializer $serializer = null): string {
37+
}
38+
39+
public function mergeIn(self|TempladoCollection ...$toImport): self {
40+
}
41+
42+
public function applyViewModel(object $model, ?Selector $selector = null): self {
43+
}
44+
45+
public function applyTransformation(Transformation $transformation, ?Selector $selector = null): self {
46+
}
47+
48+
public function applyFormData(FormData $formData): self {
49+
}
50+
51+
public function applyCSRFProtection(CSRFProtection $protection): self {
52+
}
53+
54+
}

src/TempladoCollection.php

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php declare(strict_types = 1);
2+
namespace Templado\Engine;
3+
4+
class TempladoCollection {
5+
6+
}

src/Transformation.php

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php declare(strict_types = 1);
2+
namespace Templado\Engine;
3+
4+
use DOMNode;
5+
6+
interface Transformation {
7+
public function getSelector(): Selector;
8+
9+
/** @psalm-suppress MissingReturnType Adding void here would qualify as a BC break :-/ */
10+
public function apply(DOMNode $context);
11+
}

src/csrfprotection/CSRFProtection.php

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php declare(strict_types = 1);
2+
namespace Templado\Engine;
3+
4+
class CSRFProtection {
5+
6+
/** @var string */
7+
private $fieldName;
8+
9+
/** @var string */
10+
private $tokenValue;
11+
12+
public function __construct(string $fieldName, string $tokenValue) {
13+
$this->fieldName = $fieldName;
14+
$this->tokenValue = $tokenValue;
15+
}
16+
17+
public function getFieldName(): string {
18+
return $this->fieldName;
19+
}
20+
21+
public function getTokenValue(): string {
22+
return $this->tokenValue;
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php declare(strict_types = 1);
2+
namespace Templado\Engine;
3+
4+
use DOMElement;
5+
use DOMXPath;
6+
7+
/** @psalm-suppress MissingConstructor */
8+
class CSRFProtectionRenderer {
9+
10+
/** @var CSRFProtection */
11+
private $protection;
12+
13+
/** @var DOMXPath */
14+
private $xp;
15+
16+
public function render(DOMElement $context, CSRFProtection $protection): void {
17+
$this->protection = $protection;
18+
$this->xp = new DOMXPath($context->ownerDocument);
19+
20+
foreach ($context->getElementsByTagName('form') as $form) {
21+
$this->getCSRFField($form)->setAttribute(
22+
'value',
23+
$protection->getTokenValue()
24+
);
25+
}
26+
}
27+
28+
private function getCSRFField(DOMElement $form): DOMElement {
29+
$nodeList = $this->xp->query(
30+
\sprintf('.//*[local-name() = "input" and @name="%s"]', $this->protection->getFieldName()),
31+
$form
32+
);
33+
34+
if ($nodeList->length === 0) {
35+
return $this->createField($form);
36+
}
37+
38+
/** @psalm-var \DOMElement */
39+
return $nodeList->item(0);
40+
}
41+
42+
private function createField(DOMElement $form): DOMElement {
43+
if ($form->namespaceURI !== null) {
44+
$input = $form->ownerDocument->createElementNS($form->namespaceURI, 'input');
45+
} else {
46+
$input = $form->ownerDocument->createElement('input');
47+
}
48+
49+
$form->insertBefore($input, $form->firstChild);
50+
$input->setAttribute('type', 'hidden');
51+
$input->setAttribute('name', $this->protection->getFieldName());
52+
53+
return $input;
54+
}
55+
}

src/formdata/FormData.php

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php declare(strict_types = 1);
2+
namespace Templado\Engine;
3+
4+
class FormData {
5+
6+
/** @var string */
7+
private $identifier;
8+
9+
/** @var array */
10+
private $values;
11+
12+
/**
13+
* @throws FormDataException
14+
*/
15+
public function __construct(string $identifier, array $values) {
16+
$this->identifier = $identifier;
17+
$this->values = $this->initValuesFromArray($values);
18+
}
19+
20+
public function getIdentifier(): string {
21+
return $this->identifier;
22+
}
23+
24+
public function hasKey(string $key): bool {
25+
return \array_key_exists($key, $this->values);
26+
}
27+
28+
/**
29+
* @throws FormDataException
30+
*/
31+
public function getValue(string $key): string {
32+
if (!$this->hasKey($key)) {
33+
throw new FormDataException(\sprintf('No such key: %s', $key));
34+
}
35+
36+
/** @psalm-var string */
37+
return $this->values[$key];
38+
}
39+
40+
/**
41+
* @throws FormDataException
42+
*/
43+
private function initValuesFromArray(array $values, bool $recursion = false): array {
44+
$result = [];
45+
46+
/** @psalm-suppress MixedAssignment */
47+
foreach ($values as $key => $value) {
48+
if (\is_string($value)) {
49+
$result[$key] = $value;
50+
51+
continue;
52+
}
53+
54+
if ($recursion === false && \is_array($value)) {
55+
$result[$key] = $this->initValuesFromArray($value, true);
56+
57+
continue;
58+
}
59+
60+
throw new FormDataException(
61+
\sprintf('Data type "%s" in key "%s" not supported', \gettype($value), $key)
62+
);
63+
}
64+
65+
return $result;
66+
}
67+
}

src/formdata/FormDataException.php

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php declare(strict_types = 1);
2+
namespace Templado\Engine;
3+
4+
class FormDataException extends Exception {
5+
}

0 commit comments

Comments
 (0)