diff --git a/README.md b/README.md
index be32af3..9315691 100755
--- a/README.md
+++ b/README.md
@@ -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
@@ -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
@@ -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:
@@ -130,4 +155,6 @@ print_r($updated->toArray());
---
-Now go forth, write cleaner code, and let DTO handle the messy parts.
\ No newline at end of file
+Now go forth, write cleaner code, and let DTO handle the messy parts.
+
+---
diff --git a/composer.json b/composer.json
index 167cbaa..de46b4c 100644
--- a/composer.json
+++ b/composer.json
@@ -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",
@@ -32,8 +32,7 @@
}
],
"require": {
- "php": ">=8.0",
- "maplephp/swiftrender": "^2.0"
+ "php": ">=8.0"
},
"autoload": {
"psr-4": {
diff --git a/src/Dom/Document.php b/src/Dom/Document.php
new file mode 100755
index 0000000..6a838b2
--- /dev/null
+++ b/src/Dom/Document.php
@@ -0,0 +1,237 @@
+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));
+ }
+}
diff --git a/src/Dom/Element.php b/src/Dom/Element.php
new file mode 100755
index 0000000..272628a
--- /dev/null
+++ b/src/Dom/Element.php
@@ -0,0 +1,162 @@
+elem = $elem;
+ $this->value = $value;
+ $this->snippet = $snippet;
+ }
+
+ /**
+ * Overwrite the current element
+ * @param string $elem HTML Tag name
+ */
+ public function setElement(string $elem): self
+ {
+ $this->elem = $elem;
+ return $this;
+ }
+
+ /**
+ * Set html attribute
+ * @param string $key attr key
+ * @param string|null $val attr value
+ * @return self
+ */
+ public function attr(string $key, ?string $val = null): self
+ {
+ $this->attr[$key] = $val;
+ return $this;
+ }
+
+ /**
+ * Set multiple html attributes
+ * @param array [key => value]
+ * @return self
+ */
+ public function attrArr(?array $arr): self
+ {
+ if (is_array($arr)) {
+ $this->attr = array_merge($this->attr, $arr);
+ }
+ return $this;
+ }
+
+ /**
+ * Hide html tag if its value is empty
+ * @param bool $bool
+ * @return self
+ */
+ public function hideEmptyTag(bool $bool): self
+ {
+ $this->hideEmptyTag = $bool;
+ return $this;
+ }
+
+ /**
+ * Validate hide tag value
+ * @return bool
+ */
+ protected function hideTagValid(): bool
+ {
+ return (($this->hideEmptyTag && !$this->value));
+ }
+
+ /**
+ * Add value to attr
+ * @param string $key
+ * @param string $value
+ * @param string $sep
+ * @return self
+ */
+ public function addAttr(string $key, string $value, string $sep = " "): self
+ {
+ if (isset($this->attr[$key])) {
+ $this->attr[$key] .= "{$sep}{$value}";
+ } else {
+ $this->attr[$key] = $value;
+ }
+ return $this;
+ }
+
+ /**
+ * Set elem value [VALUE]
+ * @param string|null null value can be used to auto skip HTML tag
+ * @return self
+ */
+ public function setValue(?string $value): self
+ {
+ $this->value = $value;
+ return $this;
+ }
+
+ /**
+ * Set elem value
+ * @return string
+ */
+ public function getValue(): string
+ {
+ return (string)$this->value;
+ }
+
+ /**
+ * Get elem/HTML tag
+ * @return string
+ */
+ public function getEl(): string
+ {
+ return $this->elem;
+ }
+
+ /**
+ * With cloned element or new element if is specifed
+ * @param string|null $elem
+ * @return self
+ */
+ public function withElement(?string $elem = null): self
+ {
+ $inst = clone $this;
+ if ($elem !== null) {
+ $inst->elem = $elem;
+ }
+ return $inst;
+ }
+
+ /**
+ * Array attr to string
+ * @return string
+ */
+ protected function buildAttr(): string
+ {
+ $attr = "";
+ if (count($this->attr) > 0) {
+ foreach ($this->attr as $k => $v) {
+ $attr .= " {$k}";
+ if ($v !== null) {
+ $attr .= "=\"{$v}\"";
+ }
+ }
+ }
+ return $attr;
+ }
+}
diff --git a/src/DynamicDataAbstract.php b/src/DynamicDataAbstract.php
index 74c923e..2d96dee 100755
--- a/src/DynamicDataAbstract.php
+++ b/src/DynamicDataAbstract.php
@@ -9,12 +9,11 @@
namespace MaplePHP\DTO;
-abstract class DynamicDataAbstract
+/** @psalm-no-seal-properties */
+abstract class DynamicDataAbstract extends \stdClass
{
private object $data;
- abstract public function get();
-
/**
* Create a dynamic object that holds a dynamic set of items
*/
@@ -59,11 +58,11 @@ public function __set(string $key, mixed $value): void
* Try to get data object item
*
* @param $key
- * @return null
+ * @return self
*/
- public function __get($key)
+ public function __get($key): self
{
- return ($this->data->{$key} ?? null);
+ return ($this->data->{$key} ?? $this);
}
/**
diff --git a/src/Format/Arr.php b/src/Format/Arr.php
index 6da579d..3e829db 100644
--- a/src/Format/Arr.php
+++ b/src/Format/Arr.php
@@ -39,10 +39,10 @@ public function __construct(mixed $value)
* @param array $arguments
* @return mixed
*/
- function __call(string $name, array $arguments)
+ public function __call(string $name, array $arguments)
{
$inst = new Traverse($this->raw);
- if(!method_exists($inst, $name)) {
+ if (!method_exists($inst, $name)) {
throw new \BadMethodCallException("Method '$name' does not exist.");
}
return $inst->$name(...$arguments);
diff --git a/src/Format/Clock.php b/src/Format/Clock.php
index 37f38eb..b8fa1c0 100755
--- a/src/Format/Clock.php
+++ b/src/Format/Clock.php
@@ -19,9 +19,9 @@
final class Clock extends FormatAbstract implements FormatInterface
{
- static protected ?string $defaultLocale = 'en';
+ protected static ?string $defaultLocale = 'en';
- static protected string|DateTimeZone|null $defaultTimezone = null;
+ protected static string|DateTimeZone|null $defaultTimezone = null;
protected ?string $locale = null;
protected array $parts = [];
@@ -38,7 +38,7 @@ public function __construct(mixed $value)
throw new InvalidArgumentException("Is expecting a string or a convertable string value.", 1);
}
$date = new DateTime($value);
- if(!is_null(self::$defaultTimezone)) {
+ if (self::$defaultTimezone !== null) {
$date->setTimezone(self::$defaultTimezone);
}
parent::__construct($date);
@@ -100,7 +100,7 @@ public function setLocale(string $localeCode): self
* @param string $localeCode
* @return void
*/
- static public function setDefaultLocale(string $localeCode): void
+ public static function setDefaultLocale(string $localeCode): void
{
self::$defaultLocale = $localeCode;
}
@@ -130,7 +130,7 @@ public function setTimezone(DateTimeZone|string $timezone): self
* @return void
* @throws \DateInvalidTimeZoneException
*/
- static public function setDefaultTimezone(string|DateTimeZone $timezone): void
+ public static function setDefaultTimezone(string|DateTimeZone $timezone): void
{
self::$defaultTimezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
}
@@ -154,9 +154,9 @@ public function timezone(): string
*/
public function format(string $format = 'Y-m-d H:i:s', ?string $locale = null): string
{
- $locale = !is_null($locale) ? $locale : $this->getLocale();
+ $locale = $locale !== null ? $locale : $this->getLocale();
$translations = $this->getTranslationData($this->raw, $locale);
- if($translations) {
+ if ($translations) {
return str_replace($translations['find'], $translations['replace'], $this->raw->format($format));
}
return $this->raw->format($format);
@@ -386,14 +386,14 @@ public function shortWeekday(): string
protected function getTranslationData(DateTime $date, string $locale): array
{
- if($locale !== "en") {
+ if ($locale !== "en") {
$translations = $this->getTranslation($locale);
- if($translations === false) {
+ if ($translations === false) {
$formatters = $this->getLocaleTranslation($locale);
}
}
- if(!isset($translations) && !isset($formatters)) {
+ if (!isset($translations) && !isset($formatters)) {
return [];
}
@@ -417,7 +417,7 @@ protected function getTranslationData(DateTime $date, string $locale): array
protected function getTranslation(string $locale): array|false
{
$translationFile = realpath(__DIR__ . "/../lang/$locale.php");
- if($translationFile !== false) {
+ if ($translationFile !== false) {
return require $translationFile;
}
return false;
@@ -441,7 +441,7 @@ protected function getLocaleTranslation(string $locale): array
'monthFull' => new IntlDateFormatter($locale, IntlDateFormatter::LONG, IntlDateFormatter::NONE, null, null, 'MMMM'),
'monthShort' => new IntlDateFormatter($locale, IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE, null, null, 'MMM'),
'weekdayFull' => new IntlDateFormatter($locale, IntlDateFormatter::FULL, IntlDateFormatter::NONE, null, null, 'EEEE'),
- 'weekdayShort'=> new IntlDateFormatter($locale, IntlDateFormatter::FULL, IntlDateFormatter::NONE, null, null, 'E')
+ 'weekdayShort' => new IntlDateFormatter($locale, IntlDateFormatter::FULL, IntlDateFormatter::NONE, null, null, 'E')
];
}
return $this->parts[$locale];
diff --git a/src/Format/Dom.php b/src/Format/Dom.php
index 2649b97..b6ec825 100755
--- a/src/Format/Dom.php
+++ b/src/Format/Dom.php
@@ -9,9 +9,9 @@
namespace MaplePHP\DTO\Format;
-use MaplePHP\Output\Dom\Document;
use InvalidArgumentException;
-use MaplePHP\Output\Interfaces\ElementInterface;
+use MaplePHP\DTO\Dom\Document;
+use MaplePHP\DTO\Interfaces\ElementInterface;
final class Dom extends FormatAbstract
{
@@ -152,7 +152,7 @@ public function element(): ElementInterface
{
$this->str = Str::value($this->raw);
$elem = $this->dom->create($this->tag, $this->str)->hideEmptyTag(true);
- if($this->attr) {
+ if ($this->attr) {
$elem->attrArr($this->attr);
}
return $elem;
diff --git a/src/Format/Encode.php b/src/Format/Encode.php
index 9670ed2..61434a8 100755
--- a/src/Format/Encode.php
+++ b/src/Format/Encode.php
@@ -101,7 +101,7 @@ public function urldecode(?callable $callback = null): string|array
if (is_array($this->raw)) {
$this->raw = Arr::value($this->raw)->walk(function ($value) use ($callback) {
$value = Str::value((string)$value)->rawurldecode()->get();
- if (!is_null($callback)) {
+ if ($callback !== null) {
$value = $callback($value);
}
return $value;
@@ -110,7 +110,7 @@ public function urldecode(?callable $callback = null): string|array
} else {
$this->raw = Str::value($this->raw)->rawurldecode()->get();
- if (!is_null($callback)) {
+ if ($callback !== null) {
$this->raw = $callback($this->raw);
}
}
diff --git a/src/Format/Local.php b/src/Format/Local.php
index 10e9c23..7610df9 100755
--- a/src/Format/Local.php
+++ b/src/Format/Local.php
@@ -30,7 +30,7 @@ public static function value(mixed $data)
{
if (is_string($data)) {
- if (is_null(self::$dir)) {
+ if (self::$dir === null) {
throw new Exception("You need to set default lang directory.", 1);
}
if (!is_file(self::$dir . "{$data}.php")) {
@@ -44,7 +44,7 @@ public static function value(mixed $data)
}
}
- if (is_null(self::$data[$data])) {
+ if (self::$data[$data] === null) {
throw new Exception("Could not propagate the language data object with any information.", 1);
}
@@ -89,10 +89,10 @@ public function getValue(string $key): ?string
public function get(string|array $key, string $fallback = "", ?array $sprint = null): ?string
{
- if (is_null($this::$prefix)) {
+ if ($this::$prefix === null) {
throw new Exception("Lang prefix is null.", 1);
}
- if (!is_null($sprint)) {
+ if ($sprint !== null) {
$this->sprint($sprint);
}
@@ -105,7 +105,7 @@ public function get(string|array $key, string $fallback = "", ?array $sprint = n
}
$value = ($this->value[$key][$this::$prefix] ?? $fallback);
- if (is_null($sprint)) {
+ if ($sprint === null) {
return $value;
}
diff --git a/src/Format/Num.php b/src/Format/Num.php
index e8b0948..a34542b 100755
--- a/src/Format/Num.php
+++ b/src/Format/Num.php
@@ -1,4 +1,5 @@
numInst)) {
+ if ($this->numInst !== null) {
return $this->numInst;
}
- if(is_null(self::$defNumInst)) {
+ if (self::$defNumInst === null) {
throw new \InvalidArgumentException("NumberFormatter instance not set.");
}
return self::$defNumInst;
diff --git a/src/Format/Str.php b/src/Format/Str.php
index b945319..8768dc1 100755
--- a/src/Format/Str.php
+++ b/src/Format/Str.php
@@ -35,6 +35,9 @@ public function __construct(mixed $value)
*/
public static function value(mixed $value): FormatInterface
{
+ if (is_array($value) || is_object($value)) {
+ $value = "";
+ }
return new Str((string)$value);
}
@@ -83,6 +86,25 @@ public function positionLast(string $needle, ?string $encoding = null): self
return $inst;
}
+
+ /**
+ * Get part of string
+ * This method uses multibyte functionality and provides a polyfill if your environment lacks support.
+ *
+ * @param int $start The start position of the substring
+ * @param int|null $length The length of the substring. If null, extract all characters to the end
+ * @param string|null $encoding The character encoding (e.g., 'UTF-8'). Default is null
+ * @return self
+ * @throws ErrorException
+ */
+ public function substr(int $start, ?int $length = null, ?string $encoding = null): self
+ {
+ $inst = clone $this;
+ $mb = new MB($inst->raw);
+ $inst->raw = (string)$mb->substr($start, $length, $encoding);
+ return $inst;
+ }
+
/**
* Get string length
* This method uses multibyte functionality and provides a polyfill if your environment lacks support.
@@ -151,6 +173,24 @@ public function getContains(string $needle): self
return $inst;
}
+
+ /**
+ * Get a substring that appears after the first occurrence of needle
+ * Returns null if the needle is not found in the string
+ *
+ * @param string $needle The substring to search for
+ * @param int $offset Additional offset to add after needle position (default: 0)
+ * @return self
+ * @throws ErrorException
+ */
+ public function getContainAfter(string $needle, int $offset = 0): self
+ {
+ $inst = clone $this;
+ $position = $this->position($needle)->get();
+ $inst->raw = ($position !== false) ? $inst->substr($position + 1 + $offset)->get() : null;
+ return $inst;
+ }
+
/**
* Checks if a string starts with a given substring and return needle if true else false
*
@@ -354,7 +394,7 @@ public function normalizeSeparators(): self
* @param bool $doubleEncode
* @return self
*/
- public function entityEncode(int $flags = ENT_QUOTES|ENT_SUBSTITUTE, ?string $encoding = null, bool $doubleEncode = true): self
+ public function entityEncode(int $flags = ENT_QUOTES | ENT_SUBSTITUTE, ?string $encoding = null, bool $doubleEncode = true): self
{
$inst = clone $this;
$inst->raw = htmlentities($inst->strVal(), $flags, $encoding, $doubleEncode);
@@ -368,7 +408,7 @@ public function entityEncode(int $flags = ENT_QUOTES|ENT_SUBSTITUTE, ?string $en
* @param string|null $encoding
* @return self
*/
- public function entityDecode(int $flags = ENT_QUOTES|ENT_SUBSTITUTE, ?string $encoding = null): self
+ public function entityDecode(int $flags = ENT_QUOTES | ENT_SUBSTITUTE, ?string $encoding = null): self
{
$inst = clone $this;
$inst->raw = html_entity_decode($inst->strVal(), $flags, $encoding);
@@ -439,7 +479,7 @@ public function toUpper(): self
}
/**
- * Uppercase first letter in text
+ * Uppercase the first letter in text
*
* @return self
*/
@@ -451,7 +491,7 @@ public function ucFirst(): self
}
/**
- * Uppercase first letter in every word
+ * Uppercase the first letter in every word
*
* @return self
*/
@@ -615,7 +655,7 @@ public function replace(array|string $find, array|string $replace): self
{
$inst = clone $this;
$inst->raw = str_replace($find, $replace, $inst->strVal());
- if(!is_string($inst->raw)) {
+ if (!is_string($inst->raw)) {
throw new InvalidArgumentException("The value has to be an string value!", 1);
}
return $inst;
@@ -768,7 +808,7 @@ public function getUrlParts(array $parts): self
foreach ($parts as $part) {
$method = 'getUrl' . ucfirst($part);
$subInst = new self($inst->raw);
- if(!method_exists($subInst, $method)) {
+ if (!method_exists($subInst, $method)) {
throw new InvalidArgumentException("The part '$part' does not exist as a part in getUrlParts.", 1);
}
$subInst = call_user_func([$subInst, $method]);
@@ -798,7 +838,7 @@ public function getDirname(): self
public function escape(): self
{
$inst = clone $this;
- if (is_null($inst->raw)) {
+ if ($inst->raw === null) {
$inst->raw = null;
}
return $inst->specialchars();
@@ -810,6 +850,34 @@ public function xss(): self
return $this->escape();
}
+ /**
+ * Export a variable as a valid PHP string representation
+ * This method uses PHP's var_export() function to get a parseable string representation
+ * of the raw value
+ *
+ * @return self
+ */
+ public function varExport(): self
+ {
+ $inst = clone $this;
+ $inst->raw = var_export($inst->raw, true);
+ return $inst;
+ }
+
+ /**
+ * Export raw value to string and escape special characters like newlines, tabs etc.
+ * This method is used internally to get a readable string representation of the value.
+ *
+ * @return self
+ */
+ public function exportReadableValue(): self
+ {
+ return $this->replace(
+ ["\n", "\r", "\t", "\v", "\0"],
+ ['\\n', '\\r', '\\t', '\\v', '\\0']
+ )->varExport()->replace('\\\\', '\\');
+ }
+
/**
* Return a string to bool value
*
@@ -834,7 +902,7 @@ public function jsonDecode(?bool $associative = null, int $depth = 512, int $fla
public function compare(string|int|float|bool|null $compare): self
{
$inst = clone $this;
- if(is_numeric($inst->raw)) {
+ if (is_numeric($inst->raw)) {
$inst->raw = ((float)$inst->raw > 0);
return $inst;
}
diff --git a/src/Helpers.php b/src/Helpers.php
index 30d98a4..eab7952 100644
--- a/src/Helpers.php
+++ b/src/Helpers.php
@@ -4,12 +4,12 @@
use ReflectionClass;
-class Helpers {
-
+class Helpers
+{
/**
* @throws \ReflectionException
*/
- static function debugDump($var, $label = null): void
+ public static function debugDump($var, $label = null): void
{
if (is_object($var)) {
$reflection = new ReflectionClass($var);
@@ -32,11 +32,11 @@ static function debugDump($var, $label = null): void
}
}
- static function printFormattedValue($value, $indent = 0): void
+ public static function printFormattedValue($value, $indent = 0): void
{
$spacingS = $spacingA = str_repeat(" ", $indent);
- $spacingB = str_repeat(" ", $indent+1);
- if($indent > 1) {
+ $spacingB = str_repeat(" ", $indent + 1);
+ if ($indent > 1) {
$spacingS = "";
}
if (is_array($value)) {
@@ -61,19 +61,19 @@ static function printFormattedValue($value, $indent = 0): void
* @param string $key
* @return array|bool
*/
- static function traversArrFromStr(array $array, string $key): mixed
+ public static function traversArrFromStr(array $array, string $key): mixed
{
$new = $array;
$exp = explode(".", $key);
foreach ($exp as $index) {
- if(!isset($new[$index])) {
+ $data = is_object($new) ? ($new->{$index} ?? null) : ($new[$index] ?? null);
+ if ($data === null) {
$new = false;
break;
}
- $new = $new[$index];
+ $new = $data;
}
return $new;
}
}
-
diff --git a/src/Iconv.php b/src/Iconv.php
index 519e2fe..b1348fa 100755
--- a/src/Iconv.php
+++ b/src/Iconv.php
@@ -1,4 +1,5 @@
getValue();
+ return (string)$this->get();
}
/**
* Get value
* @return string|false
*/
- public function getValue(): string|false
+ public function get(): string|false
{
return $this->value;
}
+ public function getValue(): string|false
+ {
+ return $this->get();
+ }
+
/**
* Will disable vanilla iconv function, used mostly for testing.
*
@@ -73,7 +79,7 @@ public function disableVanilla(bool $disable): void
public function encode(string $fromEncoding, string $toEncoding): self
{
$inst = $this->setEncoding($toEncoding);
- if(function_exists('iconv') && !$inst->disableVanilla) {
+ if (function_exists('iconv') && !$inst->disableVanilla) {
$inst->value = iconv($fromEncoding, $toEncoding, $inst->value);
} else {
@@ -86,11 +92,11 @@ public function encode(string $fromEncoding, string $toEncoding): self
throw new ErrorException("iconv(): Detected an illegal character in input string");
}
*/
- if($fromEncoding !== "utf-8") {
+ if ($fromEncoding !== "utf-8") {
// Convert input to UTF-8
$inMap = $inst->getMap("from", $fromEncoding);
$inst->value = $inst->encodeUtf8($inMap, $inst->value);
- if($inMap === false) {
+ if ($inMap === false) {
throw new ErrorException('iconv_strlen(): Wrong encoding, conversion from "' . $fromEncoding . '"');
}
@@ -102,7 +108,7 @@ public function encode(string $fromEncoding, string $toEncoding): self
if ($toEncoding !== 'utf-8') {
$outMap = $inst->getMap("to", $toEncoding);
$inst->value = $inst->decodeUtf8($inst->value, $outMap, ($translit ? $inst->getMapFile("translit") : []));
- if($outMap === false) {
+ if ($outMap === false) {
throw new ErrorException('iconv_strlen(): Wrong encoding, conversion to "' . $toEncoding . '"');
}
}
@@ -113,7 +119,7 @@ public function encode(string $fromEncoding, string $toEncoding): self
protected function setEncoding(?string $encoding = null): self
{
$inst = clone $this;
- if(is_string($encoding)) {
+ if (is_string($encoding)) {
$inst->encoding = $encoding;
}
return $inst;
@@ -131,12 +137,12 @@ protected function setEncoding(?string $encoding = null): self
public function strlen(?string $encoding = null): int|false
{
$inst = $this->setEncoding($encoding);
- if(function_exists("iconv_strlen") && !$inst->disableVanilla) {
+ if (function_exists("iconv_strlen") && !$inst->disableVanilla) {
return iconv_strlen($inst->value, $inst->encoding);
}
- if(is_string($encoding)) {
+ if (is_string($encoding)) {
$inst = $this->encode("utf-8", $inst->encoding);
- if($inst->getValue() === false) {
+ if ($inst->getValue() === false) {
return false;
}
}
@@ -159,19 +165,19 @@ public function substr(int $start, ?int $length = null, ?string $encoding = null
$value = "";
$inst = $this->setEncoding($encoding);
$length = $inst->getLength($length);
- if(function_exists("iconv_substr") && !$inst->disableVanilla) {
+ if (function_exists("iconv_substr") && !$inst->disableVanilla) {
$value = (string)iconv_substr($inst->value, $start, $length, $inst->encoding);
} else {
- if(is_string($encoding)) {
+ if (is_string($encoding)) {
$inst = $inst->encode("utf-8", $inst->encoding);
}
$inc = 0;
$inst->loop($inst->value, function ($character, $charCount) use (&$value, &$inc, $start, $length) {
- if(($charCount + 1) > $start) {
+ if (($charCount + 1) > $start) {
$value .= $character;
$inc++;
- if($inc >= $length) {
+ if ($inc >= $length) {
return $inc;
}
}
@@ -201,19 +207,19 @@ public function strpos(string $needle, int $offset = 0, ?string $encoding = null
$inc = 0;
$total = 0;
$completed = false;
- if(function_exists("iconv_strpos") && !$inst->disableVanilla) {
+ if (function_exists("iconv_strpos") && !$inst->disableVanilla) {
return iconv_strpos($inst->value, $needle, $offset, $inst->encoding);
}
- if(is_string($encoding)) {
+ if (is_string($encoding)) {
$inst = $inst->encode("utf-8", $inst->encoding);
}
$needleInst = new self($needle);
- if(is_string($encoding)) {
+ if (is_string($encoding)) {
$needleInst->encode("utf-8", $inst->encoding);
}
$needleLength = $needleInst->strlen();
- if($offset < 0) {
+ if ($offset < 0) {
$offset = ($inst->strlen() + $offset);
}
@@ -227,13 +233,13 @@ public function strpos(string $needle, int $offset = 0, ?string $encoding = null
$encoding
) {
- if(($charCount + 1) > $offset) {
+ if (($charCount + 1) > $offset) {
$char = (string)$needleInst->substr($inc, 1);
- if($character === $char) {
+ if ($character === $char) {
$inc++;
- if($inc === $needleLength) {
+ if ($inc === $needleLength) {
$completed = ($charCount + 1) - $inc;
- if(!$this->strposFollowThrough) {
+ if (!$this->strposFollowThrough) {
return $completed;
}
}
@@ -244,7 +250,7 @@ public function strpos(string $needle, int $offset = 0, ?string $encoding = null
$total++;
return false;
});
- if($offset > $total) {
+ if ($offset > $total) {
throw new ValueError('iconv_strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack)');
}
return ($completed && $inc > 0) ? $completed : false;
@@ -264,7 +270,7 @@ public function strrpos(string $needle, ?string $encoding = null): false|int
{
$inst = $this->setEncoding($encoding);
$inst = $inst->strposFollowThrough(true);
- if(function_exists("iconv_strrpos") && !$inst->disableVanilla) {
+ if (function_exists("iconv_strrpos") && !$inst->disableVanilla) {
return iconv_strrpos($inst->value, $needle, $inst->encoding);
}
return $inst->strpos($needle, 0, $encoding);
@@ -280,7 +286,7 @@ public function strrpos(string $needle, ?string $encoding = null): false|int
public function clearSuffix(string &$string, string $suffix): bool
{
$length = strlen($suffix);
- if(substr($string, -$length) === $suffix) {
+ if (substr($string, -$length) === $suffix) {
$string = substr($string, 0, -$length);
return true;
}
@@ -310,8 +316,8 @@ final protected function strposFollowThrough(bool $followThrough): self
*/
private function getMap(string $type, string $charset): array|false
{
- if($map = $this->getMapFile("from.$charset")) {
- if($type === "to") {
+ if ($map = $this->getMapFile("from.$charset")) {
+ if ($type === "to") {
return array_flip($map);
}
return $map;
@@ -474,7 +480,7 @@ protected function loop(string $value, ?callable $call = null): int
if (is_callable($call)) {
$returnValue = $call($character, $charCount, $int);
- if($returnValue !== false) {
+ if ($returnValue !== false) {
$charCount = $returnValue;
break;
}
@@ -503,6 +509,6 @@ final public function hasIllegalChar(string $value): bool
*/
final public function getLength(?int $length = null): int
{
- return (is_null($length) || $length > self::STRING_MAX_LENGTH) ? self::STRING_MAX_LENGTH : $length;
+ return ($length === null || $length > self::STRING_MAX_LENGTH) ? self::STRING_MAX_LENGTH : $length;
}
}
diff --git a/src/Interfaces/DocumentInterface.php b/src/Interfaces/DocumentInterface.php
new file mode 100755
index 0000000..4ccc1ca
--- /dev/null
+++ b/src/Interfaces/DocumentInterface.php
@@ -0,0 +1,76 @@
+ value]
+ * @return self
+ */
+ public function attrArr(?array $arr): self;
+
+ /**
+ * Hide html tag if its value is empty
+ * @param bool $bool
+ * @return self
+ */
+ public function hideEmptyTag(bool $bool): self;
+
+ /**
+ * Add value to attr
+ * @param string $key
+ * @param string $value
+ * @param string $sep
+ * @return self
+ */
+ public function addAttr(string $key, string $value, string $sep = " "): self;
+
+ /**
+ * Set elem value [VALUE]
+ * @param string|null null value can be used to auto skip HTML tag
+ * @return self
+ */
+ public function setValue(?string $value): self;
+
+ /**
+ * Set elem value
+ * @return string
+ */
+ public function getValue(): string;
+
+ /**
+ * Get elem/HTML tag
+ * @return string
+ */
+ public function getEl(): string;
+
+ /**
+ * With cloned element or new element if is specifed
+ * @param string|null $elem
+ * @return self
+ */
+ public function withElement(?string $elem = null): self;
+}
diff --git a/src/MB.php b/src/MB.php
index 45548f2..14af275 100755
--- a/src/MB.php
+++ b/src/MB.php
@@ -1,4 +1,5 @@
getValue();
+ return (string)$this->get();
}
/**
* Get value
* @return string|false
*/
- public function getValue(): string|false
+ public function get(): string|false
{
return $this->value;
}
+ public function getValue(): string|false
+ {
+ return $this->get();
+ }
+
/**
* Will disable vanilla iconv function, used mostly for testing.
*
@@ -67,7 +73,7 @@ public function disableVanilla(bool $disable): void
public function encode(string $fromEncoding, string $toEncoding): self
{
$inst = clone $this;
- if(function_exists('mb_convert_encoding') && !$inst->disableVanilla) {
+ if (function_exists('mb_convert_encoding') && !$inst->disableVanilla) {
$inst->value = mb_convert_encoding($inst->value, $toEncoding, $fromEncoding);
} else {
$inst->iconv = $inst->iconv->encode($fromEncoding, $toEncoding);
@@ -85,7 +91,7 @@ public function encode(string $fromEncoding, string $toEncoding): self
*/
public function strlen(?string $encoding = null): int|false
{
- if(function_exists('mb_strlen') && !$this->disableVanilla) {
+ if (function_exists('mb_strlen') && !$this->disableVanilla) {
return mb_strlen($this->value, $encoding);
}
return $this->iconv->strlen($encoding);
@@ -103,7 +109,7 @@ public function strlen(?string $encoding = null): int|false
public function substr(int $start, ?int $length = null, ?string $encoding = null): self
{
$inst = clone $this;
- if(function_exists('mb_substr') && !$inst->disableVanilla) {
+ if (function_exists('mb_substr') && !$inst->disableVanilla) {
$inst->value = mb_substr($inst->value, $start, $length, $encoding);
} else {
$this->iconv = $this->iconv->substr($start, $length, $encoding);
@@ -123,7 +129,7 @@ public function substr(int $start, ?int $length = null, ?string $encoding = null
*/
public function strpos(string $needle, int $offset = 0, ?string $encoding = null): false|int
{
- if(function_exists('mb_strpos') && !$this->disableVanilla) {
+ if (function_exists('mb_strpos') && !$this->disableVanilla) {
return mb_strpos($this->value, $needle, $offset, $encoding);
}
return $this->iconv->strpos($needle, $offset, $encoding);
@@ -139,7 +145,7 @@ public function strpos(string $needle, int $offset = 0, ?string $encoding = null
*/
public function strrpos(string $needle, ?string $encoding = null): false|int
{
- if(function_exists('mb_strrpos') && !$this->disableVanilla) {
+ if (function_exists('mb_strrpos') && !$this->disableVanilla) {
return mb_strrpos($this->value, $needle, 0, $encoding);
}
return $this->iconv->strrpos($needle, $encoding);
diff --git a/src/Traits/CollectionUtilities.php b/src/Traits/CollectionUtilities.php
index 8d498aa..61466cb 100644
--- a/src/Traits/CollectionUtilities.php
+++ b/src/Traits/CollectionUtilities.php
@@ -3,6 +3,7 @@
namespace MaplePHP\DTO\Traits;
use Closure;
+use ErrorException;
use MaplePHP\DTO\Format\Arr;
use MaplePHP\DTO\Helpers;
use MaplePHP\DTO\Traverse;
@@ -16,7 +17,7 @@ trait CollectionUtilities
*
* @param callable $callback A callable to run for each element in each array.
* @param array $array Supplementary variable list of array arguments
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function map(callable $callback, array ...$array): self
{
@@ -30,15 +31,15 @@ public function map(callable $callback, array ...$array): self
* https://www.php.net/manual/en/function.array-filter.php
*
* @param callable|null $callback The callback function to use
- * If no callback is supplied, all empty entries of array will be
+ * If no callback is supplied, all empty entries of an array will be
* removed. See empty() for how PHP defines empty in this case.
* @param int $mode Flag determining what arguments are sent to callback:
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function filter(?callable $callback = null, int $mode = 0): self
{
$inst = clone $this;
- $data = is_null($callback) ? $inst->raw : $inst->fetch();
+ $data = $callback === null ? $inst->raw : $inst->fetch();
$inst->raw = array_filter($data, $callback, $mode);
return $inst;
}
@@ -49,7 +50,7 @@ public function filter(?callable $callback = null, int $mode = 0): self
*
* @param callable $callback
* @param mixed|null $initial
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function reduce(callable $callback, mixed $initial = null): self
{
@@ -64,7 +65,7 @@ public function reduce(callable $callback, mixed $initial = null): self
*
* @param int $length
* @param bool $preserveKeys
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function chunk(int $length, bool $preserveKeys = false): self
{
@@ -77,7 +78,7 @@ public function chunk(int $length, bool $preserveKeys = false): self
* Flatten a array
*
* @param bool $preserveKeys
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function flatten(bool $preserveKeys = false): self
{
@@ -99,7 +100,7 @@ public function flatten(bool $preserveKeys = false): self
/**
* Flatten array and preserve keys
*
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function flattenWithKeys(): self
{
@@ -110,8 +111,8 @@ public function flattenWithKeys(): self
* Merge two arrays
*
* @param array|TraverseInterface $combine
- * @param bool $before Merge before main collection
- * @return Arr|CollectionUtilities|Traverse
+ * @param bool $before Merge before the main collection
+ * @return Arr|self|Traverse
*/
public function merge(array|TraverseInterface $combine, bool $before = false): self
{
@@ -126,10 +127,10 @@ public function merge(array|TraverseInterface $combine, bool $before = false): s
}
/**
- * Append array after main collection
+ * Append array after the main collection
*
* @param array|TraverseInterface $combine
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function append(array|TraverseInterface $combine): self
{
@@ -137,10 +138,10 @@ public function append(array|TraverseInterface $combine): self
}
/**
- * Perpend array after main collection
+ * Perpend array after the main collection
*
* @param array|TraverseInterface $combine
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function prepend(array|TraverseInterface $combine): self
{
@@ -155,10 +156,13 @@ public function prepend(array|TraverseInterface $combine): self
* @param int|null $length
* @param mixed $replacement
* @param mixed|null $splicedResults
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function splice(
- int $offset, ?int $length, mixed $replacement = [], mixed &$splicedResults = null
+ int $offset,
+ ?int $length,
+ mixed $replacement = [],
+ mixed &$splicedResults = null
): self {
$inst = clone $this;
$splicedResults = array_splice($inst->raw, $offset, $length, $replacement);
@@ -173,7 +177,7 @@ public function splice(
* @param int $offset
* @param int|null $length
* @param bool $preserveKeys
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function slice(int $offset, ?int $length, bool $preserveKeys = false): self
{
@@ -187,7 +191,7 @@ public function slice(int $offset, ?int $length, bool $preserveKeys = false): se
* https://www.php.net/manual/en/function.array-diff.php
*
* @param array|TraverseInterface $array
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function diff(array|TraverseInterface $array): self
{
@@ -202,7 +206,7 @@ public function diff(array|TraverseInterface $array): self
* https://www.php.net/manual/en/function.array-diff-assoc.php
*
* @param array|TraverseInterface $array
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function diffAssoc(array|TraverseInterface $array): self
{
@@ -217,7 +221,7 @@ public function diffAssoc(array|TraverseInterface $array): self
* https://www.php.net/manual/en/function.array-diff-key.php
*
* @param array|TraverseInterface $array
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function diffKey(array|TraverseInterface $array): self
{
@@ -232,7 +236,7 @@ public function diffKey(array|TraverseInterface $array): self
* https://www.php.net/manual/en/function.array-unique.php
*
* @param int $flags
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function unique(int $flags = SORT_STRING): self
{
@@ -245,7 +249,7 @@ public function unique(int $flags = SORT_STRING): self
/**
* Will only return duplicate items
*
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function duplicates(): self
{
@@ -258,7 +262,7 @@ public function duplicates(): self
* Exchanges all keys with their associated values in an array
* https://www.php.net/manual/en/function.array-flip.php
*
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function flip(): self
{
@@ -272,7 +276,7 @@ public function flip(): self
* https://www.php.net/manual/en/function.unset.php
*
* @param string ...$keySpread
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function unset(string|int|float|array ...$keySpread): self
{
@@ -286,10 +290,10 @@ public function unset(string|int|float|array ...$keySpread): self
}
/**
- * Will explode an array item value and then merge it into array in same hierarchy
+ * Will explode an array item value and then merge it into an array in the same hierarchy
*
* @param string $separator
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function expMerge(string $separator): self
{
@@ -314,7 +318,7 @@ public function arrayItemExpMerge(string $separator): self
*
* @param int|string|null $columnKey
* @param int|string|null $indexKey
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function column(int|string|null $columnKey, int|string|null $indexKey = null): self
{
@@ -332,11 +336,11 @@ public function pluck(int|string|null $columnKey, int|string|null $indexKey = nu
}
/**
- * Shift an element off the beginning of array
+ * Shift an element off the beginning of an array
* https://www.php.net/manual/en/function.array-shift.php
*
* @param mixed $value
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function shift(mixed &$value = null): self
{
@@ -346,11 +350,11 @@ public function shift(mixed &$value = null): self
}
/**
- * Pop the element off the end of array
+ * Pop the element off the end of an array
* https://www.php.net/manual/en/function.array-pop.php
*
* @param mixed $value
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function pop(mixed &$value = null): self
{
@@ -364,7 +368,7 @@ public function pop(mixed &$value = null): self
* https://www.php.net/manual/en/function.array-unshift.php
*
* @param mixed $value
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function unshift(mixed ...$value): self
{
@@ -374,11 +378,11 @@ public function unshift(mixed ...$value): self
}
/**
- * Push one or more elements onto the end of array
+ * Push one or more elements onto the end of an array
* https://www.php.net/manual/en/function.array-push.php
*
* @param mixed $value
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function push(mixed ...$value): self
{
@@ -388,12 +392,12 @@ public function push(mixed ...$value): self
}
/**
- * Pad array to the specified length with a value
+ * Pad an array to the specified length with a value
* https://www.php.net/manual/en/function.array-pad.php
*
* @param int $length
* @param mixed $value
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function pad(int $length, mixed $value): self
{
@@ -409,7 +413,7 @@ public function pad(int $length, mixed $value): self
* @param int $startIndex
* @param int $count
* @param mixed $value
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function fill(int $startIndex, int $count, mixed $value): self
{
@@ -425,7 +429,7 @@ public function fill(int $startIndex, int $count, mixed $value): self
* @param string|int|float $start
* @param string|int|float $end
* @param int|float $step
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function range(string|int|float $start, string|int|float $end, int|float $step = 1): self
{
@@ -438,7 +442,7 @@ public function range(string|int|float $start, string|int|float $end, int|float
* Shuffle an array
* https://www.php.net/manual/en/function.shuffle.php
*
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function shuffle(): self
{
@@ -452,7 +456,7 @@ public function shuffle(): self
* https://www.php.net/manual/en/function.array-rand.php
*
* @param int $num
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function rand(int $num = 1): self
{
@@ -466,7 +470,7 @@ public function rand(int $num = 1): self
* https://www.php.net/manual/en/function.array-replace.php
*
* @param array ...$replacements
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function replace(array ...$replacements): self
{
@@ -480,7 +484,7 @@ public function replace(array ...$replacements): self
* https://www.php.net/manual/en/function.array-replace.php
*
* @param array ...$replacements
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function replaceRecursive(array ...$replacements): self
{
@@ -494,7 +498,7 @@ public function replaceRecursive(array ...$replacements): self
* https://www.php.net/manual/en/function.array-reverse.php
*
* @param bool $preserveKeys
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function reverse(bool $preserveKeys = false): self
{
@@ -505,12 +509,12 @@ public function reverse(bool $preserveKeys = false): self
/**
* Searches the array for a given value and returns the first corresponding 'key' if
- * successful to collection
+ * successful to a collection
* https://www.php.net/manual/en/function.array-search.php
*
* @param mixed $needle
* @param bool $strict
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function search(mixed $needle, bool $strict = false): self
{
@@ -521,17 +525,17 @@ public function search(mixed $needle, bool $strict = false): self
/**
* Searches the array for a given value and returns the first corresponding 'value' if
- * successful to collection
+ * successful to a collection
*
* @param mixed $needle
* @param bool $strict
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function find(mixed $needle, bool $strict = false): self
{
$inst = clone $this;
$key = $this->search($needle, $strict)->get();
- if($key === false) {
+ if ($key === false) {
$inst->raw = null;
return $inst;
}
@@ -539,57 +543,57 @@ public function find(mixed $needle, bool $strict = false): self
}
/**
- * Searches and filter out the array items that is found
+ * Searches and filter out the array items that are found
* https://www.php.net/manual/en/function.array-search.php
*
* @param mixed $needle
* @param bool $strict
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function searchFilter(array $needle, bool $strict = false): self
{
- return $this->filter(function ($item) use($needle, $strict) {
+ return $this->filter(function ($item) use ($needle, $strict) {
return !in_array($item, $needle, $strict);
});
}
/**
- * Searches and filter out the array items that is not found
+ * Searches and filter out the array items that are not found
* https://www.php.net/manual/en/function.array-search.php
*
* @param mixed $needle
* @param bool $strict
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function searchMatch(array $needle, bool $strict = false): self
{
- return $this->filter(function ($item) use($needle, $strict) {
+ return $this->filter(function ($item) use ($needle, $strict) {
return in_array($item, $needle, $strict);
});
}
/**
- * Apply a user supplied function to every member of an array
+ * Apply a user-supplied function to every member of an array
* https://www.php.net/manual/en/function.array-walk.php
*
* @param array $needle
* @param bool $strict
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function select(array $needle, bool $strict = false): self
{
- return $this->filter(function ($keyItem) use($needle, $strict) {
+ return $this->filter(function ($keyItem) use ($needle, $strict) {
return in_array($keyItem, $needle, $strict);
}, ARRAY_FILTER_USE_KEY);
}
/**
- * Apply a user supplied function to every member of an array
+ * Apply a user-supplied function to every member of an array
* https://www.php.net/manual/en/function.array-walk.php
*
* @param Closure $call
* @param mixed|null $arg
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function walk(Closure $call, mixed $arg = null): self
{
@@ -606,7 +610,7 @@ public function walk(Closure $call, mixed $arg = null): self
*
* @param Closure $call
* @param mixed|null $arg
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function walkRecursive(Closure $call, mixed $arg = null): self
{
@@ -618,8 +622,8 @@ public function walkRecursive(Closure $call, mixed $arg = null): self
}
/**
- * Get first item in collection
- * @return Arr|CollectionUtilities|Traverse
+ * Get the first item in a collection
+ * @return Arr|self|Traverse
*/
public function next(): self
{
@@ -629,8 +633,8 @@ public function next(): self
}
/**
- * Get first item in collection
- * @return Arr|CollectionUtilities|Traverse
+ * Get the first item in a collection
+ * @return Arr|self|Traverse
*/
public function prev(): self
{
@@ -644,7 +648,7 @@ public function prev(): self
* https://www.php.net/manual/en/function.reset.php
*
* @param mixed|null $value
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function reset(mixed &$value = null): self
{
@@ -658,7 +662,7 @@ public function reset(mixed &$value = null): self
* https://www.php.net/manual/en/function.end.php
*
* @param mixed|null $value
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function end(mixed &$value = null): self
{
@@ -668,10 +672,10 @@ public function end(mixed &$value = null): self
}
/**
- * Get first item in collection
+ * Get the first item in a collection
* https://www.php.net/manual/en/function.end.php
*
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function first(): self
{
@@ -681,10 +685,10 @@ public function first(): self
}
/**
- * Get last item in collection
+ * Get the last item in a collection
* https://www.php.net/manual/en/function.end.php
*
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function last(): self
{
@@ -697,7 +701,7 @@ public function last(): self
* Fetch a key from an array
* https://www.php.net/manual/en/function.key.php
*
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function key(): self
{
@@ -714,7 +718,7 @@ public function key(): self
* Return all the keys or a subset of the keys of an array
* https://www.php.net/manual/en/function.array-keys.php
*
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function keys(): self
{
@@ -732,7 +736,7 @@ public function keys(): self
* https://www.php.net/implode
*
* @param array|string $separator
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function implode(array|string $separator = ""): self
{
@@ -742,26 +746,31 @@ public function implode(array|string $separator = ""): self
}
/**
- * Will return array item at index/key
+ * Will return the array item at index/key
* https://www.php.net/manual/en/function.shuffle.php
*
* @param int|float|string $key
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function eq(int|float|string $key): self
{
if (is_string($key) && str_contains($key, ".")) {
return new self(Helpers::traversArrFromStr($this->toArray(), $key));
}
+
+ if(is_object($this->raw)) {
+ return new self($this->raw->{$key} ?? false);
+ }
+
return new self($this->raw[$key] ?? false);
}
/**
- * Extract all array items values in array with a wildcard search ("2025-*")
+ * Extract all array items values in an array with a wildcard search ("2025-*")
*
* @param string $search wildcard prefix
* @param bool $searchByKey
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function wildcardMatch(string $search, bool $searchByKey = false): self
{
@@ -779,16 +788,16 @@ public function wildcardMatch(string $search, bool $searchByKey = false): self
}
// Alias it wildcardMatch
- function wildcardSearch(string $search, bool $searchByKey = false): self
+ public function wildcardSearch(string $search, bool $searchByKey = false): self
{
return $this->wildcardMatch($search, $searchByKey);
}
/**
- * Find all array keys array with a wildcard search ("prefix_*")
+ * Find all array keys with a wildcard search ("prefix_*")
*
* @param string $search wildcard prefix
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function wildcardMatchKeys(string $search): self
{
@@ -796,10 +805,10 @@ public function wildcardMatchKeys(string $search): self
}
/**
- * Create a fallback value if value is Empty/Null/0/false
+ * Create a fallback value if the value is Empty/Null/0/false
*
* @param string $fallback
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function fallback(mixed $fallback): self
{
@@ -814,7 +823,7 @@ public function fallback(mixed $fallback): self
* Sprint over values
*
* @param string $add
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function sprint(string $add): self
{
@@ -825,20 +834,29 @@ public function sprint(string $add): self
return $inst;
}
+ public function parseStr(): self
+ {
+ $inst = clone $this;
+ if (is_string($inst->raw)) {
+ parse_str($this->toString(), $inst->raw);
+ }
+ return $inst;
+ }
+
/**
* Same as value validate but will method chain.
- * If invalid then the value will be set to "null" OR whatever you set the fallback
+ * If invalid, then the value will be set to "null" OR whatever you set the fallback
*
* @param string $method
* @param array $args
* @param mixed|null $fallback
- * @return Arr|CollectionUtilities|Traverse
- * @throws \ErrorException
+ * @return Arr|self|Traverse
+ * @throws ErrorException
*/
public function validOrFallback(string $method, array $args = [], mixed $fallback = null): self
{
$inst = clone $this;
- if(!$this->valid($method, $args)) {
+ if (!$this->valid($method, $args)) {
$inst->raw = $fallback;
}
return $inst;
@@ -848,13 +866,13 @@ public function validOrFallback(string $method, array $args = [], mixed $fallbac
* Calculate the sum of values in an array
* https://www.php.net/manual/en/function.array-sum.php
*
- * @return Arr|CollectionUtilities|Traverse
+ * @return Arr|self|Traverse
*/
public function sum(): self
{
$inst = clone $this;
$arr = $this->raw;
- if(!is_array($arr)) {
+ if (!is_array($arr)) {
$arr = $this->toArray();
}
$inst->raw = array_sum($arr);
@@ -875,4 +893,4 @@ protected function handleCollectArg(array|TraverseInterface $collect): array
return $collect;
}
-}
\ No newline at end of file
+}
diff --git a/src/Traverse.php b/src/Traverse.php
index 3be6031..f80b9a0 100755
--- a/src/Traverse.php
+++ b/src/Traverse.php
@@ -1,23 +1,171 @@
getData()->{$key})) {
+ if (isset($this->getData()->{$key})) {
$data = $this->getData()->{$key};
- if(is_object($data) && !($data instanceof DynamicDataAbstract)) {
+ if (is_object($data) && !($data instanceof DynamicDataAbstract)) {
return $data;
}
return $this::value($data);
}
- if(isset($this->raw[$key]) || isset($this->raw->{$key})) {
- return $this::value($this->raw[$key] ?? $this->raw->{$key});
+ if (
+ (is_array($this->raw) && isset($this->raw[$key])) ||
+ (is_object($this->raw) && isset($this->raw->{$key}))
+ ) {
+ return $this::value(
+ is_array($this->raw) ? $this->raw[$key] : $this->raw->{$key}
+ );
}
$this->raw = null;
@@ -129,12 +281,12 @@ public function __call($method, $args)
$expectedClass = array_shift($data);
$formatClassInst = $this->format($expectedClass, $this->raw);
$expectedMethod = implode('', $data);
- if(!$expectedMethod) {
+ if (!$expectedMethod) {
return $formatClassInst;
}
$expectedMethod = lcfirst($expectedMethod);
- if(!method_exists($formatClassInst, $expectedMethod) &&
+ if (!method_exists($formatClassInst, $expectedMethod) &&
($formatClassInst === "Collection" && !function_exists($expectedMethod))) {
throw new BadMethodCallException("The DTO method \"$expectedMethod\" does not exist!", 1);
}
@@ -171,7 +323,25 @@ public function add(string $key, mixed $value): self
}
/**
- * Validate current item and set to fallback (default: null) if not valid
+ * Validate the current item
+ *
+ * @example $this->email->validator()->isEmail() // returns bool
+ * @return Validator
+ * @throws ErrorException
+ */
+ public function validator(): Validator
+ {
+ return Validator::value($this->raw);
+ }
+
+ // Alias to validator
+ public function expect(): Validator
+ {
+ return Validator::value($this->raw);
+ }
+
+ /**
+ * Validate the current item and set to fallback (default: null) if not valid
*
* @param string $method
* @param array $args
@@ -180,8 +350,8 @@ public function add(string $key, mixed $value): self
*/
public function valid(string $method, array $args = []): bool
{
- $inp = Inp::value($this->raw);
- if(!method_exists($inp, $method)) {
+ $inp = Validator::value($this->raw);
+ if (!method_exists($inp, $method)) {
throw new BadMethodCallException("The MaplePHP validation method \"$method\" does not exist!", 1);
}
return $inp->{$method}(...$args);
@@ -192,7 +362,6 @@ public function valid(string $method, array $args = []): bool
*
* https://www.php.net/manual/en/function.json-encode.php
*
- * @param mixed $value
* @param int $flags
* @param int $depth
* @return string|false
@@ -202,6 +371,16 @@ public function toJson(int $flags = 0, int $depth = 512): string|false
return json_encode($this->get(), $flags, $depth);
}
+ /**
+ * Returns the string representation of the value
+ *
+ * @return string
+ */
+ public function toString(): string
+ {
+ return (string)$this->get();
+ }
+
/**
* Returns the int representation of the value
*
@@ -230,17 +409,17 @@ public function toFloat(): float
public function toBool(): bool
{
$value = $this->get();
- if(is_bool($value)) {
+ if (is_bool($value)) {
return $value;
}
- if(is_numeric($value)) {
+ if (is_numeric($value)) {
return ((float)$value > 0);
}
return ($value !== "false" && strlen($value));
}
/**
- * Convert collection into an array
+ * Convert a collection into an array
*
* @param callable|null $callback
* @return array
@@ -251,11 +430,11 @@ public function toArray(?callable $callback = null): array
$new = [];
$inst = clone $this;
- if (is_null($inst->raw)) {
+ if ($inst->raw === null) {
$inst->raw = $inst->getData();
}
- if(!is_object($inst->raw) && !is_array($inst->raw)) {
+ if (!is_object($inst->raw) && !is_array($inst->raw)) {
$inst->raw = [$inst->raw];
}
@@ -264,7 +443,7 @@ public function toArray(?callable $callback = null): array
(($get = $callback($row, $key, $index)) !== false)) {
$row = $get;
}
- if($row instanceof self) {
+ if ($row instanceof self) {
$row = $row->get();
}
$new[$key] = $row;
@@ -273,10 +452,70 @@ public function toArray(?callable $callback = null): array
return $new;
}
+ /**
+ * Accepts and validates data types
+ *
+ * This method checks if the raw data type matches any of the valid data types provided.
+ * If matched, returns the raw value, otherwise throws an exception.
+ *
+ * @template T of 'array'|'object'|'bool'|'int'|'float'|'string'|'resource'|'null'|'callable'|'closure'
+ * @param array $validDataType List of valid data types to check against
+ * @psalm-param list $validDataType
+ * @return mixed The raw value if type matches, otherwise throws exception
+ * @throws BadMethodCallException If data type is not supported
+ * @psalm-return (
+ * T is 'array'? array:
+ * T is 'object'? object:
+ * T is 'bool'? bool:
+ * T is 'int'? int:
+ * T is 'float'? float:
+ * T is 'string'? string:
+ * T is 'resource'? mixed:
+ * T is 'null'? null:
+ * T is 'callable'? callable:
+ * T is 'closure'? \Closure:
+ * mixed
+ * )
+ */
+ public function acceptType(array $validDataType = []): mixed
+ {
+ if (is_array($this->raw) && in_array("array", $validDataType)) {
+ return $this->raw;
+ }
+ if (is_object($this->raw) && in_array("object", $validDataType)) {
+ return $this->raw;
+ }
+ if (is_bool($this->raw) && in_array("bool", $validDataType)) {
+ return $this->raw;
+ }
+ if (is_int($this->raw) && in_array("int", $validDataType)) {
+ return $this->raw;
+ }
+ if (is_float($this->raw) && in_array("float", $validDataType)) {
+ return $this->raw;
+ }
+ if (is_string($this->raw) && in_array("string", $validDataType)) {
+ return $this->raw;
+ }
+ if ($this->raw === null && in_array("null", $validDataType)) {
+ return $this->raw;
+ }
+ if (is_callable($this->raw) && in_array("callable", $validDataType)) {
+ return $this->raw;
+ }
+ if (($this->raw instanceof Closure) && in_array("closure", $validDataType)) {
+ return $this->raw;
+ }
+ if (is_resource($this->raw) && in_array("resource", $validDataType)) {
+ return $this->raw;
+ }
+ throw new BadMethodCallException("The DTO data type is not supported!", 1);
+ }
+
/**
* Immutable: Access incremental array
*
- * @param callable|null $callback Access array row in the callbacks argument
+ * @param callable|null $callback Access array row in the callback argument
* @return array|object|null
*/
public function fetch(?callable $callback = null): array|object|null
@@ -285,12 +524,12 @@ public function fetch(?callable $callback = null): array|object|null
$new = [];
$inst = clone $this;
- if (is_null($inst->raw)) {
+ if ($inst->raw === null) {
$inst->raw = $inst->getData();
}
foreach ($inst->raw as $key => $row) {
- if (!is_null($callback)) {
+ if ($callback !== null) {
if (($get = $callback($inst::value($row), $key, $row, $index)) !== false) {
$new[$key] = $get;
} else {
@@ -305,7 +544,7 @@ public function fetch(?callable $callback = null): array|object|null
$value = $row;
} else {
// Incremental -> value
- $value = !is_null($row) ? Format\Str::value($row) : null;
+ $value = $row !== null ? Format\Str::value($row) : null;
}
$new[$key] = $value;
}
@@ -328,7 +567,7 @@ public function each(callable $callback): array|object|null
}
/**
- * Dump collection into a human-readable array dump
+ * Dump a collection into a human-readable array dump
*
* @return void
* @throws ReflectionException
@@ -339,7 +578,7 @@ public function dump(): void
}
/**
- * Count if row is array. Can be used to validate before @fetch method
+ * Count if the row is an array. Can be used to validate before @fetch method
*
* @return int
*/
@@ -371,7 +610,7 @@ protected function format(string $dtoClassName, mixed $value): object
$name = ucfirst($dtoClassName);
$className = "MaplePHP\\DTO\\Format\\$name";
- if(!in_array($name, self::$helpers)) {
+ if (!in_array($name, self::$helpers)) {
throw new BadMethodCallException("The DTO class \"$dtoClassName\" is not a Helper class! " .
"You can add helper class with 'addHelper' if you wish.", 1);
}
diff --git a/src/TraverseInterface.php b/src/TraverseInterface.php
index fe1474f..bd352d4 100644
--- a/src/TraverseInterface.php
+++ b/src/TraverseInterface.php
@@ -8,13 +8,12 @@
interface TraverseInterface
{
-
/**
* Object traverser
* @param $key
- * @return Traverse|null
+ * @return Traverse
*/
- public function __get($key);
+ public function __get($key): self;
/**
* Immutable formating class
@@ -93,4 +92,4 @@ public function isset(): mixed;
* @return self
*/
public function fallback(mixed $fallback): self;
-}
\ No newline at end of file
+}
diff --git a/tests/unitary-dto-string.php b/tests/unitary-dto-string.php
index 636503b..9bb0195 100644
--- a/tests/unitary-dto-string.php
+++ b/tests/unitary-dto-string.php
@@ -1,6 +1,7 @@
'{"name":"Alice","email":"alice@example.com","roles":["admin","editor"]}',
"date" => "2023-08-21 14:35:12",
"content" => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse euismod turpis eget elit eleifend, non pulvinar enim dapibus.\n Nullam blandit vitae justo vitae viverra. Aliquam varius eu leo a euismod.",
+ "exportReadable" => "hello \n\t",
]);
$this->add($obj->firstname->str()->stripTags()->ucFirst()->get(), [
@@ -53,6 +55,18 @@
"equal" => 18,
], "strlen: Failed");
+ $this->add($obj->content->str()->substr(0, 5)->get(), [
+ "equal" => "Lorem",
+ ], "substr: Failed");
+
+ $this->add($obj->email->str()->getContainAfter("@")->get(), [
+ "equal" => "gmail.com",
+ ], "getContainAfter required to be true");
+
+ $this->add($obj->exportReadable->str()->exportReadableValue()->get(), [
+ "equal" => Str::value("hello \n\t")->exportReadableValue()->get(),
+ ], "exportReadableValue required to be true");
+
$data = $obj->json->str()->jsonDecode()->get();
$this->add($data->name ?? "", [
"equal" => 'Alice',
diff --git a/tests/unitary-dto-traverse.php b/tests/unitary-dto-traverse.php
index c2de6fd..476137e 100644
--- a/tests/unitary-dto-traverse.php
+++ b/tests/unitary-dto-traverse.php
@@ -2,6 +2,7 @@
use MaplePHP\DTO\Format\Arr;
use MaplePHP\DTO\Traverse;
+use MaplePHP\Unitary\Expect;
$unit = new MaplePHP\Unitary\Unit();
@@ -11,6 +12,7 @@
"firstname" => "daniel",
"lastname" => "doe",
"email" => "john.doe@gmail.com",
+ "contact_email" => "john.doe@gmail.com",
"slug" => "Lorem ipsum åäö",
"price" => "1999.99",
"publish_date" => "2023-08-01 14:35:12",
@@ -30,6 +32,14 @@
'randSumList' => [12, 77, 62, 626],
]);
+ $this->validate($obj->contact_email->toString(), function(Expect $inst) {
+ $inst->isEmail();
+ });
+
+
+ $this->validate($obj->email->validator()->isEmail(), function($inst) {
+ return $inst->isTrue();
+ });
$this->add($obj->meta->wildcardSearch("2023-*")->count(), [
'equal' => 2,
@@ -328,7 +338,7 @@
}, "Value should equal to 'john 1'");
- $this->add($obj->feed->fetch(), function ($value) {
+ $this->add($obj->feed->fetch(), function ($value, $inst) {
return ($this->isArray() && count($value) === 2);
}, "Expect fetch to return an array");
@@ -425,4 +435,12 @@
"equal" => "fish"
], "last did return wrong value");
+
+ /*
+ echo $this->listAllProxyMethods(\MaplePHP\DTO\Format\Str::class, "str");
+ echo $this->listAllProxyMethods(\MaplePHP\DTO\Format\Num::class, "num");
+ echo $this->listAllProxyMethods(\MaplePHP\DTO\Format\Num::class, "clock");
+ echo $this->listAllProxyMethods(\MaplePHP\DTO\Format\Num::class, "dom");
+ */
+
});
\ No newline at end of file