Skip to content

v3.1.0 #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 34 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

DTO stands for **Darn Tidy Object**, a playful twist on the traditional Data Transfer Object. But this isn’t your average DTO. It’s a fully-loaded toolkit for **traversing, transforming, and tidying up structured data** in PHP with style, power, and simplicity.

_It also makes your life easier by ensuring every piece of data is returned in the correct type-helping. Whether you expect an int, string, bool, or even a callable, DTO gives you strict, reliable access to your data with minimal effort._

---

## 📦 Installation

Expand All @@ -10,13 +13,15 @@ composer require maplephp/dto
```

## 📘 Documentation
- [Why DTO?](https://maplephp.github.io/DTO/docs/intro#why-dto)
- [Traverse Collection](https://maplephp.github.io/DTO/docs/traverse)
- [Format string](https://maplephp.github.io/DTO/docs/format-string)
- [Format Number](https://maplephp.github.io/DTO/docs/format-number)
- [Format Clock](https://maplephp.github.io/DTO/docs/format-clock)
- [Format Dom](https://maplephp.github.io/DTO/docs/format-dom)

* [Why DTO?](https://maplephp.github.io/DTO/docs/intro#why-dto)
* [Traverse Collection](https://maplephp.github.io/DTO/docs/traverse)
* [Format string](https://maplephp.github.io/DTO/docs/format-string)
* [Format Number](https://maplephp.github.io/DTO/docs/format-number)
* [Format Clock](https://maplephp.github.io/DTO/docs/format-clock)
* [Format Dom](https://maplephp.github.io/DTO/docs/format-dom)

---

## How It Works

Expand Down Expand Up @@ -60,6 +65,26 @@ echo $obj->article->content->strExcerpt()->strUcFirst();

---

### Correct Type Handling (with ease)

No more clunky `is_numeric` checks or `intval` casts. DTO makes it simple to extract values in the exact type you expect:

```php
$orderId = $dto->order->id->toInt();
// Result: 1234 (int)
```

Handle flexible types cleanly with fallbacks:

```php
$callback = $dto->settings->onReady->acceptType(['callable', 'null']);
if (is_callable($callback)) {
$callback(); // Result: Runs a startup hook or closure
}
```

---

### Built-In Data Transformation

Transform values directly using built-in helpers like:
Expand Down Expand Up @@ -130,4 +155,6 @@ print_r($updated->toArray());

---

Now go forth, write cleaner code, and let DTO handle the messy parts.
Now go forth, write cleaner code, and let DTO handle the messy parts.

---
5 changes: 2 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "maplephp/dto",
"type": "library",
"version": "v3.0.0",
"version": "v3.1.0",
"description": "DTO library in PHP provides benefits such as encapsulating data, enforcing immutability and facilitating data transformation.",
"keywords": [
"dto",
Expand Down Expand Up @@ -32,8 +32,7 @@
}
],
"require": {
"php": ">=8.0",
"maplephp/swiftrender": "^2.0"
"php": ">=8.0"
},
"autoload": {
"psr-4": {
Expand Down
237 changes: 237 additions & 0 deletions src/Dom/Document.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
<?php

/**
* @Package: MaplePHP - DOM Main class
* @Author: Daniel Ronkainen
* @Licence: Apache-2.0 license, Copyright © Daniel Ronkainen
Don't delete this comment, its part of the license.
*/

namespace MaplePHP\DTO\Dom;

use MaplePHP\DTO\Interfaces\DocumentInterface;
use MaplePHP\DTO\Interfaces\ElementInterface;

class Document implements DocumentInterface
{
public const TAG_NO_ENDING = [
"meta",
"link",
"img",
"br",
"hr",
"input",
"keygen",
"param",
"source",
"track",
"embed"
];

protected $elements;
private $html;
private $elem;
private static $inst;

/**
* Will output get
* @return string
*/
public function __toString(): string
{
return $this->get();
}

/**
* Get get Dom/document (Will only trigger execute once per instance)
* @return string
*/
public function get(): string
{
if ($this->html === null) {
$this->execute();
}
return $this->html;
}

/**
* Init DOM instance
* @param string $key DOM access key
* @return self
*/
public static function dom(string $key): self
{
if (empty(self::$inst[$key])) {
self::$inst[$key] = self::withDom($key);
}
return self::$inst[$key];
}

/**
* Init DOM instance
* @param string $key DOM access key
* @return self
*/
public static function withDom(string $key): self
{
self::$inst[$key] = new self();
return self::$inst[$key];
}

/**
* Create and bind tag to a key so it can be overwritten
* @param string $tag HTML tag (without brackets)
* @param string $key Bind tag to key
* @param bool|boolean $prepend Prepend instead of append
* @return ElementInterface
*/
public function bindTag(string $tag, string $key, bool $prepend = false): ElementInterface
{
if ($prepend) {
$this->elem = $this->createPrepend($tag, null, $key);
} else {
$this->elem = $this->create($tag, null, $key);
}
return $this->elem;
}

/**
* Create (append) element
*
* @param string $element HTML tag (without brackets)
* @param null $value add value to tag
* @param string|null $bind
* @return ElementInterface
*/
public function create($element, $value = null, ?string $bind = null): ElementInterface
{
$inst = new Element($element, $value);

if ($bind !== null) {
$this->elements[$bind] = $inst;
} else {
$this->elements[] = $inst;
}

return $inst;
}

/**
* Prepend element first
* @param string $element HTML tag (without brackets)
* @param string $value add value to tag
* @return ElementInterface
*/
public function createPrepend(string $element, ?string $value = null, ?string $bind = null): ElementInterface
{
$inst = new Element($element, $value);
if ($this->elements === null) {
$this->elements = [];
}
if ($bind !== null) {
//$new[$bind] = $inst;
$this->elements = array_merge([$bind => $inst], $this->elements);
} else {
$this->elements = array_merge([$inst], $this->elements);
}

return $inst;
}

/**
* Get one element from key
* @return ElementInterface|null
*/
public function getElement(string $key): ?ElementInterface
{
return ($this->elements[$key] ?? null);
}

/**
* Get all elements
* @return array
*/
public function getElements(): array
{
return $this->elements;
}

/**
* Get html tag
* @param string $key
* @return string|null
*/
public function getTag(string $key): ?string
{
return ($this->el[$key] ?? null);
}

/**
* Execute and get Dom/document
* @param callable|null $call Can be used to manipulate element within feed
* @return string
*/
public function execute(?callable $call = null): string
{
$this->html = "";

if ($this->elements === null) {
if (method_exists($this, "withElement")) {
$inst = $this->withElement();
$this->elements[] = $inst;
}
}
if (is_array($this->elements)) {
$this->build($this->elements, $call);
}

return $this->html;
}

/**
* Build document
* @param array $arr elements
* @param callable|null $call Can be used to manipulate element within feed
* @return void
*/
private function build(array $arr, ?callable $call = null): void
{
foreach ($arr as $key => $elemObj) {
$hasNoEnding = $this->elemHasEnding($elemObj->getEl());
$this->buildCallable($elemObj, $key, $hasNoEnding, $call);

if (!$elemObj->hideTagValid()) {
$this->html .= "\t<" . $elemObj->getEl() . $elemObj->buildAttr() . ">";
}
if (!$hasNoEnding) {
$this->html .= $elemObj->getValue();
}
if (isset($elemObj->elements)) {
$this->build($elemObj->elements, $call);
}
if (!$hasNoEnding && !$elemObj->hideTagValid()) {
$this->html .= "</" . $elemObj->getEl() . ">\n";
}
if ($hasNoEnding && !$elemObj->hideTagValid()) {
$this->html .= "\n";
}
}
}

private function buildCallable($elemObj, $key, $hasNoEnding, ?callable $call): void
{
if ($call !== null) {
$call($elemObj, $key, $hasNoEnding);
}
}

/**
* Validate if element has ending
* @param string $elem
* @return bool
*/
final protected function elemHasEnding(string $elem): bool
{
return (in_array($elem, $this::TAG_NO_ENDING));
}
}
Loading