Skip to content

Commit

Permalink
Simplified casting of bind value (#399)
Browse files Browse the repository at this point in the history
* build: upgrade dom requirement and loosen version range

* docs: update examples

* feature: trim whitespace when there are only template children
closes #363

* feature: simplify casting in value binding
closes #398
  • Loading branch information
g105b authored Jan 30, 2023
1 parent b92a98b commit dd5d399
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 5 deletions.
45 changes: 45 additions & 0 deletions examples/binding/12-bindData-object.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php
use Gt\Dom\HTMLDocument;
use Gt\DomTemplate\Bind;
use Gt\DomTemplate\DocumentBinder;

require __DIR__ . "/../../vendor/autoload.php";

// EXAMPLE CODE: https://github.com/PhpGt/DomTemplate/wiki/Binding-objects

class Customer {
public function __construct(
public readonly string $id,
public readonly string $name,
public readonly DateTime $createdAt,
) {}

#[Bind("years-active")]
public function calculateYearsSinceCreation():int {
$now = new DateTime();
$diff = $now->diff($this->createdAt);
return $diff->y;
}
}

$html = <<<HTML
<h1>Welcome back, <span data-bind:text="name">your name</span>!</h1>
<p>You have been a customer for <time data-bind:text="createdAt">0</time> years.</p>
HTML;

function example(DocumentBinder $binder):void {
// The $customer variable could come from a database in a real-world project.
$customer = new Customer(
id: "abc123",
name: "Cody",
createdAt: new DateTime("2016-07-06 15:04:00"),
);
$binder->bindData($customer);
}

$document = new HTMLDocument($html);
$binder = new DocumentBinder($document);

example($binder);

echo $document;
21 changes: 21 additions & 0 deletions src/BindValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
namespace Gt\DomTemplate;

use DateTimeInterface;
use Stringable;

class BindValue implements Stringable {
public function __construct(
private readonly mixed $rawValue
) {}

public function __toString():string {
$value = $this->rawValue ?? "";

if($value instanceof DateTimeInterface) {
$value = $value->format(DateTimeInterface::RFC7231);
}

return $value;
}
}
10 changes: 6 additions & 4 deletions src/BindableCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ public function isBindable(object $object):bool {
}
foreach($refObj->getProperties() as $refProp) {
$propName = $refProp->getName();
if($refProp->isPublic() && $refProp->isReadOnly() && $refProp->isInitialized($object) && (is_null($object->$propName) || is_scalar($object->$propName) || $object->$propName instanceof Stringable)) {
$bindKey = $propName;
$attributeCache[$bindKey]
= fn(object $object, $key):?string => $object->$key;
if($refProp->isPublic() && $refProp->isInitialized($object)) {
if(is_null($object->$propName) || is_scalar($object->$propName) || $object->$propName instanceof Stringable) {
$bindKey = $propName;
$attributeCache[$bindKey]
= fn(object $object, $key):?string => $object->$key;
}
}
$refAttributes = $this->getBindAttributes($refProp);

Expand Down
4 changes: 4 additions & 0 deletions src/ElementBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public function bind(
mixed $value,
Element $context
):void {
if(!is_null($value) && !is_scalar($value) && !is_iterable($value)) {
$value = new BindValue($value);
}

/** @var Element $element */
foreach($this->htmlAttributeCollection->find($context) as $element) {
$this->htmlAttributeBinder->expandAttributes($element);
Expand Down
4 changes: 3 additions & 1 deletion src/HTMLAttributeBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use Gt\Dom\DOMTokenList;
use Gt\Dom\DOMTokenListFactory;
use Gt\Dom\Element;
use Gt\Dom\ElementType;

class HTMLAttributeBinder {
private TableBinder $tableBinder;
Expand All @@ -19,6 +18,9 @@ public function bind(
if(is_null($value)) {
return;
}
if(!is_scalar($value) && !is_iterable($value)) {
$value = new BindValue($value);
}

if($element instanceof Document) {
$element = $element->documentElement;
Expand Down
4 changes: 4 additions & 0 deletions src/ListBinder.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace Gt\DomTemplate;

use DateTimeInterface;
use Gt\Dom\Document;
use Gt\Dom\Element;
use Iterator;
Expand Down Expand Up @@ -127,6 +128,9 @@ private function isKVP(mixed $item):bool {
return false;
}

if($item instanceof DateTimeInterface) {
return false;
}
if($item instanceof Stringable) {
return false;
}
Expand Down
10 changes: 10 additions & 0 deletions test/phpunit/HTMLAttributeBinderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,14 @@ public function testBind_modifierQuestion_withValue():void {
$sut->bind("alternativeText", $testMessage, $img);
self::assertSame($testMessage, $img->alt);
}

public function testBind_dateTimeInterface():void {
$dateTime = new DateTime("1988-04-05 17:23:00");

$document = new HTMLDocument(DocumentTestFactory::HTML_SINGLE_ELEMENT);
$outputElement = $document->querySelector("output");
$sut = new HTMLAttributeBinder();
$sut->bind(null, $dateTime, $outputElement);
self::assertSame("Tue, 05 Apr 1988 17:23:00 GMT", $outputElement->textContent);
}
}
24 changes: 24 additions & 0 deletions test/phpunit/ListBinderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use ArrayIterator;
use DateInterval;
use DateTime;
use DateTimeInterface;
use Gt\Dom\Element;
use Gt\Dom\HTMLDocument;
use Gt\DomTemplate\Bind;
Expand Down Expand Up @@ -560,6 +561,29 @@ public function __toString():string {
}
}

public function testBindListData_dateTimeAutomatic():void {
$document = new HTMLDocument(DocumentTestFactory::HTML_DATES);
$templateCollection = new TemplateCollection($document);
/** @var array<DateTimeInterface> $listData */
$listData = [];

$dateTime = new DateTime();
$currentYear = $dateTime->format("Y");
$dateTime->setDate($currentYear, 1, 1);

while($dateTime->format("Y") === $currentYear) {
array_push($listData, clone $dateTime);
$dateTime->add(new DateInterval("P1M"));
}

$sut = new ListBinder($templateCollection);
$sut->bindListData($listData, $document);

foreach($document->querySelectorAll("li") as $i => $li) {
self::assertSame($listData[$i]->format(DateTimeInterface::RFC7231), $li->textContent);
}
}

public function testBindListData_todoList():void {
$document = new HTMLDocument(DocumentTestFactory::HTML_TODO);
$templateCollection = new TemplateCollection($document);
Expand Down

0 comments on commit dd5d399

Please sign in to comment.