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/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..9dd546b 100755 --- a/src/Format/Dom.php +++ b/src/Format/Dom.php @@ -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/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