diff --git a/README.md b/README.md index 3ea24c3..524a7ef 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,86 @@ I will be really happy hearing from you. ---- +## Important notes + 1. Currently JSON serialization and deserialization does not work properly. + Please do not rely on it for now! **IMPORTANT!** + When fix for this problem comes, and you are using current logic - you might get + into a broken code logic. Please do not use `\spaf\simputils\PHP::serialize()` and + `\spaf\simputils\PHP::deserialize()` code with JSON mechanics, you can switch the + mechanics to native PHP like this (workaround): + ```php + PHP::$serialization_mechanism = PHP::SERIALIZATION_TYPE_PHP; + PHP::init(); + ``` + That will use native PHP mechanics for serialization, which should work properly + starting from this release (1.1.3) + ## Changelog +### 1.1.3 + + * Implemented method `\spaf\simputils\models\Box::batch()` that allows to easily export items + of specified keys to the local variable scope + * Implemented methods `setFromData()` and meta-magic methods `___serialize()` and + `___deserialize()` to fix PHP native serialization/deserialization for the + following classes: + * `\spaf\simputils\models\Version` + * `\spaf\simputils\models\UrlObject` + * `\spaf\simputils\models\Time` + * `\spaf\simputils\models\L10n` + * `\spaf\simputils\models\IPv4Range` + * `\spaf\simputils\models\IPv4` + * `\spaf\simputils\models\File` + * `\spaf\simputils\models\Dir` + * `\spaf\simputils\models\DateTimeZone` + * `\spaf\simputils\models\DateTime` + * `\spaf\simputils\models\DatePeriod` + * `\spaf\simputils\models\DateInterval` + * `\spaf\simputils\models\Date` + * `\spaf\simputils\models\DataUnit` + * `\spaf\simputils\models\BigNumber` + * Code Sniffer is removed from the project (got really annoyed, and it does not work correctly) + * `\spaf\simputils\models\Time` and `\spaf\simputils\models\Date` have been refactored a bit. + The caching mechanics has been fixed. + * Additionally have been added the properties for `\spaf\simputils\models\Date` + and `\spaf\simputils\models\Time` from the target `DateTime` object + * `\spaf\simputils\models\Date` and `\spaf\simputils\models\Time` result of `for_system` + now returns the whole DateTime string value of UTC, not only the date or time component. + * Implemented `\spaf\simputils\generic\BasicExecEnvHandler` Execution-Environment (aka stages), + besides that implemented `\spaf\simputils\generic\BasicInitConfig::@$ee` property that + automatically will be assigned during `PHP::init()`, the object or params could be + adjusted in the incoming config, example: + ```php + $ic = PHP::init([ + 'l10n' => 'AT', + // 'ee' => new DummyExecEnvHandler(false, ee_name: 'TOO'), + 'ee' => [ + 'ee' => 'test3-local', + 'is_hierarchical' => true, + 'permitted_values' => [ + 'test1', + 'test2', + 'test3', + 'test4', + ] + ] + ]); + pd("{$ic->ee}", Boolean::to($ic->ee->is('test4-local'))); + ``` + For now not much of documentation is provided, but you always can define your own + implementation of the class like `\spaf\simputils\components\execenvs\DummyExecEnvHandler` + to handle your Exec-Env/stages implementation! More documentation and example will follow. + * Additionally implemented `\spaf\simputils\components\execenvs\DummyExecEnvHandler` + which is a dummy handler that just returns the predefined value. Should not be used + on production. + * Implemented `\spaf\simputils\exceptions\ExecEnvException` exception for Exec-Env cases + * Implemented `\spaf\simputils\models\Box::popFromStart()` and + `\spaf\simputils\models\Box::popFromEnd()` methods to get value from the box, return + and remove it from the box. + * Implemented tests for: + * Exec-Env + * Box batch functionality + ### 1.1.2 * Implemented `\spaf\simputils\basic\with` functionality of a transactional style like @@ -137,13 +215,14 @@ so documentation will come after that in the very nearest time. My apologies. Minimal PHP version: **8.0** -Current framework version: **1.1.2** +Current framework version: **1.1.3** ```shell composer require spaf/simputils "^1" ``` Keep in mind that the library development suppose to follow the semantic versioning, -so the functionality within the same major version - should be backward-compatible. +so the functionality within the same major version - should be backward-compatible (Except +cases of bugs and issues). More about semantic versioning: [Semantic Versioning Explanation](https://semver.org). diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index a362d35..0000000 --- a/phpcs.xml +++ /dev/null @@ -1,130 +0,0 @@ - - - SPAF general - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ^models/files/apps/settings/DotEnvSettings.php - diff --git a/src/PHP.php b/src/PHP.php index 70e5642..5877108 100644 --- a/src/PHP.php +++ b/src/PHP.php @@ -18,6 +18,7 @@ use spaf\simputils\exceptions\RedefWrongReference; use spaf\simputils\exceptions\SerializationProblem; use spaf\simputils\exceptions\UnBoxable; +use spaf\simputils\generic\BasicExecEnvHandler; use spaf\simputils\generic\BasicInitConfig; use spaf\simputils\generic\BasicIP; use spaf\simputils\generic\SimpleObject; @@ -112,7 +113,7 @@ public static function frameworkDir() { */ public static function simpUtilsVersion(): Version|string { $class = static::redef(Version::class); - return new $class('1.1.2', 'SimpUtils'); + return new $class('1.1.3', 'SimpUtils'); } /** @@ -178,6 +179,10 @@ public static function init(null|array|Box|BasicInitConfig $args = null): BasicI // $config->___setup($args ?? []); static::metaMagicSpell($config, 'setup', $args ?? []); + if (empty($config->ee)) { + $config->ee = BasicExecEnvHandler::EE_UNKNOWN; + } + // TODO Implement code below into config through Properties if (!is_dir($config->code_root)) { $config->code_root = dirname($config->code_root); @@ -192,6 +197,7 @@ public static function init(null|array|Box|BasicInitConfig $args = null): BasicI } else { // TODO Exception here? } + return $config; } @@ -618,7 +624,7 @@ public static function isArrayCompatible(mixed $var): bool { * @see \print_r() * @return void */ - public static function pd(mixed ...$args) { + static function pd(mixed ...$args) { $callback = CodeBlocksCacheIndex::getRedefinition(InitConfig::REDEF_PD); if ($callback && $callback !== InitConfig::REDEF_PD) { $res = (bool) $callback(...$args); diff --git a/src/components/execenvs/DummyExecEnvHandler.php b/src/components/execenvs/DummyExecEnvHandler.php new file mode 100644 index 0000000..6876112 --- /dev/null +++ b/src/components/execenvs/DummyExecEnvHandler.php @@ -0,0 +1,38 @@ +what_to_return; + } + + public function __toString(): string { + $res = Str::from($this->what_to_return); + if ($this->include_signature) { + return "{$this->ee_name}#{$res}"; + } + + return "{$this->ee_name}"; + } + +} diff --git a/src/exceptions/ExecEnvException.php b/src/exceptions/ExecEnvException.php new file mode 100644 index 0000000..23408e4 --- /dev/null +++ b/src/exceptions/ExecEnvException.php @@ -0,0 +1,9 @@ + self::EE_PROD, + 2 => self::EE_DEMO, + 3 => self::EE_DEV, + ]; + + public function __construct( + $ee = self::EE_UNKNOWN, + $is_hierarchical = false, + $permitted_values = null + ) { + if (is_null($permitted_values)) { + $permitted_values = $this->_permitted_values; + } + $this->_permitted_values = PHP::box($permitted_values); + $this->_permitted_values->joined_to_str = true; + + $this->_is_hierarchical = $is_hierarchical; + + if ($ee === static::EE_UNKNOWN) { + $this->_ee = static::EE_UNKNOWN; + $this->_is_local = false; + } else { + [$this->_ee, $this->_is_local] = $this->parse($ee); + $this->_is_local = (bool) $this->_is_local; + } + } + + protected function parse(string $val): array { + $res = []; + $permitted = $this->_permitted_values->clone(); + $permitted->separator = '|'; + $mask = PREG_UNMATCHED_AS_NULL; + + preg_match( + "#({$permitted})?(-local)?#i", + $val, + $res, + $mask + ); + + return PHP::box($res)->batch([1, 2], true); + } + + /** + * @inheritDoc + * + * Keep in mind that current "local" Exec-Env will return true for non-local one. + * + * In non-hierarchical "dev-local" will return true for "dev" and "dev-local", + * but will return false for all other cases. + * + * In hierarchical "dev-local" will return in the same way but including the hierarchical + * model. So "prod", "prod-local", "demo", "demo-local", "dev" and "dev-local" will return + * true for "dev-local" Exec-Env, when for hierarchical "dev" true will be returned only + * for "prod", "demo" and "dev". None of local ones will be considered. + * + * If hierarchical model is enabled, then permitted values with lower index integer value + * will be included into the higher index integer values. + * + * Like this: `prod -> demo -> dev`, so in case of "demo" all the prod stuff will + * return true, but "dev" will return false. And in case of "dev" - both "prod" + * and "demo" will be included. + * + * By default hierarchical model is disabled + * + */ + function is(string $val): bool { + [$expected_ee, $expected_is_local] = $this->parse($val); + $expected_is_local = (bool) $expected_is_local; + + if ($expected_ee === static::EE_UNKNOWN || $this->_ee === static::EE_UNKNOWN) { + $p = $this->_permitted_values->clone(); + + throw new ExecEnvException('Unknown exec-env cannot be used or checked. ' . + "Please set the proper exec-env value: {$p}"); + } + + $check = $this->_is_local || $expected_is_local === false; + + if ($this->_ee === $expected_ee) { + if ($check) { + // NOTE Then does not matter if local or not + return true; + } + } else if ($this->is_hierarchical) { + // NOTE Hierarchical + $flipped = $this->permitted_values->flipped(); + if (isset($flipped[$expected_ee]) && isset($flipped[$this->_ee])) { + if (is_numeric($flipped[$expected_ee]) && is_numeric($flipped[$this->_ee])) { + return intval($flipped[$expected_ee]) <= intval($flipped[$this->_ee]) + && $check; + } + } + } + + return false; + } + + function __toString(): string { + $res = $this->_ee; + if ($this->_is_local) { + $res = "{$res}-local"; + } + return "{$res}"; + } + +} diff --git a/src/generic/BasicInitConfig.php b/src/generic/BasicInitConfig.php index 2a70eaa..e39055d 100644 --- a/src/generic/BasicInitConfig.php +++ b/src/generic/BasicInitConfig.php @@ -8,6 +8,7 @@ use spaf\simputils\DT; use spaf\simputils\exceptions\InitConfigAlreadyInitialized; use spaf\simputils\FS; +use spaf\simputils\interfaces\ExecEnvHandlerInterface; use spaf\simputils\interfaces\InitBlockInterface; use spaf\simputils\models\BigNumber; use spaf\simputils\models\Box; @@ -18,8 +19,10 @@ use spaf\simputils\special\CommonMemoryCacheIndex; use spaf\simputils\Str; use ValueError; +use function is_array; use function is_null; use function is_numeric; +use function is_string; /** * @@ -29,6 +32,7 @@ * * @property string $big_number_extension * @property bool $data_unit_long + * @property null|ExecEnvHandlerInterface|\spaf\simputils\generic\BasicExecEnvHandler $ee Exec-Environment */ abstract class BasicInitConfig extends SimpleObject { @@ -68,6 +72,24 @@ abstract class BasicInitConfig extends SimpleObject { protected bool $_is_timezone_changed = false; + #[Property('ee', type: 'get')] + protected ?ExecEnvHandlerInterface $_ee_handler = null; + + #[Property('ee')] + protected function setEe(null|ExecEnvHandlerInterface|array|Box|string $val) { + $obj = null; + + if ($val instanceof ExecEnvHandlerInterface) { + $obj = $val; + } else if (is_string($val)) { + $obj = new BasicExecEnvHandler($val); + } else if ($val instanceof Box || is_array($val)) { + $obj = new BasicExecEnvHandler(...$val); + } + + $this->_ee_handler = $obj; + } + #[DebugHide] protected null|string $_l10n_name = null; #[DebugHide] diff --git a/src/generic/fixups/FixUpDateTimePrism.php b/src/generic/fixups/FixUpDateTimePrism.php index 8483ce8..22d5d96 100644 --- a/src/generic/fixups/FixUpDateTimePrism.php +++ b/src/generic/fixups/FixUpDateTimePrism.php @@ -4,6 +4,7 @@ use DateInterval; use DateTimeInterface; +use spaf\simputils\attributes\Property; use spaf\simputils\generic\BasicPrism; use spaf\simputils\models\DateTime; use spaf\simputils\models\DateTimeZone; @@ -12,11 +13,16 @@ /** * @codeCoverageIgnore + * + * @property \spaf\simputils\models\DateTimeZone|string $tz Timezone change */ abstract class FixUpDateTimePrism extends BasicPrism { use ForOutputsTrait; - public function __construct(DateTime|string $datetime = "now", ?DateTimeZone $timezone = null) { + public function __construct( + DateTime|string $datetime = "now", + null|string|DateTimeZone $timezone = null + ) { $class_dt = PHP::redef(DateTime::class); if ($datetime instanceof DateTime) { $this->init($datetime); @@ -44,6 +50,17 @@ public function diff(DateTimeInterface $targetObject, bool $absolute = false) { return $this->_object->diff($targetObject, $absolute); } + #[Property('tz')] + public function getTimezone(): DateTimeZone|false { + return new DateTimeZone($this->_object->getTimezone()->getName()); + } + + #[Property('tz', type: 'set')] + public function setTimezone($timezone) { + $this->_object->tz = $timezone; + return $this; + } + public function __toString(): string { return $this->getForUser(); } diff --git a/src/interfaces/ExecEnvHandlerInterface.php b/src/interfaces/ExecEnvHandlerInterface.php new file mode 100644 index 0000000..4abd875 --- /dev/null +++ b/src/interfaces/ExecEnvHandlerInterface.php @@ -0,0 +1,21 @@ +__construct($data['value'], $data['is_mutable'], $data['extension']); + return $this; + } + + function ___serialize(): Box|array { + return [ + 'value' => $this->_value, + 'is_mutable' => $this->_is_mutable, + 'extension' => $this->_ext, + ]; + } + + protected function ___deserialize(array|Box $data): static { + return $this->setFromData($data); + } + public function __toString(): string { return "{$this->_value}"; } diff --git a/src/models/Box.php b/src/models/Box.php index 30f3b14..d6a84b4 100644 --- a/src/models/Box.php +++ b/src/models/Box.php @@ -18,6 +18,7 @@ use function array_combine; use function array_flip; use function array_keys; +use function array_pop; use function array_values; use function arsort; use function count; @@ -30,6 +31,7 @@ use function is_numeric; use function is_object; use function is_string; +use function preg_replace; use function shuffle; use function uasort; @@ -836,6 +838,125 @@ function pathAlike(): self { return $this; } + /** + * Just extract and batch values to be imported in code scope + * + * Keep in mind that the items returned in the order of specified `$keys` param! + * + * **In the most cases "extract" syntax is the most preferable**, just be careful with it! + * + * Quick import example 1 (extract): + * ```php + * // PHP Extract style of assignment + * extract($b->batch(['var1', 'var2', 'var3'])); + * ``` + * + * Quick import example 2 (list): + * ```php + * // List style of assignment + * [$var1, $var2, $var3] = $b->batch(['var1', 'var2', 'var3'], true); + * ``` + * + * Bigger example: + * ```php + * PHP::init(); + * // Important, but the third value will be skipped, only assoc values are allowed for + * // batch assignment/extraction + * $b = bx([ + * 'var1' => 'value 1', + * 'value 2', + * 'var3' => 'value 3', + * 'var4' => 'value 4', + * ]); + * + * // Creating variables and assigning null to them + * $var1 = $var2 = $var3 = $var4 = null; + * + * // Both code-lines bellow will do the same! + * // List style of assignment + * [$var1, $var2, $var3] = $b->batch(['var1', 'var2', 'var3'], true); + * // PHP Extract style of assignment (slightly more elegant and easy to use) + * extract($b->batch(['var1', 'var2', 'var3'])); + * + * pd($var1, $var2, $var3, $var4); + * ``` + * + * Output: + * ``` + * value 1 + * + * value 3 + * + * ``` + * + * **Important**: For the "extract" method, never specify uncontrollably the keys like + * `PHP::POST()->keys` or `PHP::GET()->keys` - You will compromise your code, + * it's a huge security issue. Always specify controlled keys! + * [https://www.php.net/manual/en/function.extract.php](https://www.php.net/manual/en/function.extract.php) + * + * Important that the key names might be modified before extraction if they contain + * non-acceptable var symbols. If the key start from a number - the var name will be + * prefixed by the underscore, because PHP variables cannot start from numbers. + * + * But keep in mind, that keys should be provided as-is in the box/array! + * + * @param static|array $keys Keys of items to be extracted in batch + * @param bool $is_list_ready If set to true, will replace the keys with + * numeric sequential values (Order will be preserved + * as specified in the $keys) + * + * @return array + * + * @see https://www.php.net/manual/en/function.extract.php + */ + function batch($keys, $is_list_ready = false): array { + $res = []; + + foreach ($keys as $key) { + if ($is_list_ready) { + $res[] = $this->get($key); + } else { + $clean_key = $this->_cleanKeyVar($key); + $res[$clean_key] = $this->get($key); + } + } + + return $res; + } + + private function _cleanKeyVar($key): string { + // NOTE It must strip out all the impossible symbols for the variable + $res = preg_replace('#[-\s/\\\]#i', '_', $key); + $res = preg_replace('#[^a-z0-9_]#i', '', $res); + $res = preg_replace('#[^a-z0-9_]#i', '', $res); + if (is_numeric($res[0])) { + $res = "_{$res}"; + } + return $res; + } + + /** + * Get value from the ending side and remove it from the storage + * + * @return mixed + */ + function popFromEnd(): mixed { + $stash = (array) $this->shift(from_start: false)->stash; + $res = array_pop($stash); + return $res; + } + + /** + * Get value from the starting side and remove it from the storage + * + * @return mixed + */ + function popFromStart(): mixed { + $stash = (array) $this->shift(from_start: true)->stash; + $res = array_pop($stash); + return $res; + } + /** * @codeCoverageIgnore * @return string diff --git a/src/models/BoxRO.php b/src/models/BoxRO.php index 6110ada..9195a3c 100644 --- a/src/models/BoxRO.php +++ b/src/models/BoxRO.php @@ -2,6 +2,7 @@ namespace spaf\simputils\models; +use spaf\simputils\exceptions\ReadOnlyProblem; use spaf\simputils\traits\ArrayReadOnlyAccessTrait; use spaf\simputils\traits\RedefinableComponentTrait; @@ -17,6 +18,16 @@ class BoxRO extends Box { use ArrayReadOnlyAccessTrait; use RedefinableComponentTrait; + function popFromEnd(): mixed { + throw new ReadOnlyProblem('Popping from the end is not ' . + 'possible due to read-only state'); + } + + function popFromStart(): mixed { + throw new ReadOnlyProblem('Popping from the start is not ' . + 'possible due to read-only state'); + } + static function redefComponentName(): string { return InitConfig::REDEF_BRO; // @codeCoverageIgnore } diff --git a/src/models/DataUnit.php b/src/models/DataUnit.php index 4b30fb0..e371d38 100644 --- a/src/models/DataUnit.php +++ b/src/models/DataUnit.php @@ -543,6 +543,21 @@ public static function fromJson(string $json): static { return new static($res); } + function setFromData($data): static { + $this->__construct($data['value']); + return $this; + } + + function ___serialize(): Box|array { + return [ + 'value' => $this->_value, + ]; + } + + protected function ___deserialize(array|Box $data): static { + return $this->setFromData($data); + } + /** * @return string * @codeCoverageIgnore diff --git a/src/models/Date.php b/src/models/Date.php index 51f0d83..5e9f4bd 100644 --- a/src/models/Date.php +++ b/src/models/Date.php @@ -3,8 +3,8 @@ namespace spaf\simputils\models; use spaf\simputils\attributes\Property; -use spaf\simputils\DT; use spaf\simputils\generic\fixups\FixUpDateTimePrism; +use spaf\simputils\Str; /** * Date Prism @@ -14,12 +14,25 @@ * * @property-read string $for_system * @property-read string $for_user + * + * @property int $week ISO 8601 week number of year, weeks starting on Monday + * @property-read int $doy The day of the year (starting from 0) + * + * @property int $year Year + * @property int $month Month + * @property int $day Day + * + * @property-read int $dow Numeric representation of the day of the week 0 (su) - 6 (sa) + * @property-read int $dow_iso Numeric representation of the day of the week + * + * @property-read bool $is_weekend Is day a weekend + * @property-read bool $is_weekday Is day a week-day */ class Date extends FixUpDateTimePrism { #[Property('for_system')] protected function getForSystem(): string { - return $this->_object->getForSystemObj()->format(DT::FMT_DATE); + return $this->_object->getForSystemObj()->for_system; } #[Property('for_user')] @@ -27,4 +40,80 @@ protected function getForUser(): string { $obj = $this->_object; return $obj->format(DateTime::$l10n_user_date_format); } + + #[Property('week')] + protected function getWeek(): int { + return $this->_object->week; + } + + #[Property('dow')] + protected function getDow(): int { + return (int) $this->_object->dow; + } + + #[Property('dow_iso')] + protected function getDowIso(): int { + return (int) $this->_object->dow_iso; + } + + #[Property('is_weekend')] + protected function getIsWeekend(): bool { + return $this->_object->is_weekend; + } + + #[Property('is_weekday')] + protected function getIsWeekday(): bool { + return $this->_object->is_weekday; + } + + #[Property('doy')] + protected function getDoy(): int { + return $this->_object->doy; + } + + #[Property('year')] + protected function getYear(): int { + return $this->_object->year; + } + + #[Property('year')] + protected function setYear(int $year): void { + $this->_object->setDate($year, $this->month, $this->day); + } + + #[Property('month')] + protected function getMonth(): int { + return $this->_object->month; + } + + #[Property('month')] + protected function setMonth(int $month): void { + $this->_object->setDate($this->year, $month, $this->day); + } + + #[Property('day')] + protected function getDay(): int { + return $this->_object->day; + } + + #[Property('day')] + protected function setDay(int $day): void { + $this->_object->setDate($this->year, $this->month, $day); + } + + function setFromData($data): static { + $this->__construct($data['for_system'], $data['tz']); + return $this; + } + + function ___serialize(): Box|array { + return [ + 'for_system' => $this->for_system, + 'tz' => Str::ing($this->tz), + ]; + } + + protected function ___deserialize(array|Box $data): static { + return $this->setFromData($data); + } } diff --git a/src/models/DateInterval.php b/src/models/DateInterval.php index 0923c22..6aa6542 100644 --- a/src/models/DateInterval.php +++ b/src/models/DateInterval.php @@ -54,6 +54,21 @@ protected function getSpecificationString() { return DT::dateIntervalSpecificationString($this); // @codeCoverageIgnore } + function setFromData($data): static { + $this->__construct($data['value']); + return $this; + } + + function ___serialize(): Box|array { + return [ + 'value' => "{$this->specification_string}", + ]; + } + + protected function ___deserialize(array|Box $data): static { + return $this->setFromData($data); + } + public function __toString(): string { return $this->format($this->formatForString()); } diff --git a/src/models/DatePeriod.php b/src/models/DatePeriod.php index 5a3c98f..669755c 100644 --- a/src/models/DatePeriod.php +++ b/src/models/DatePeriod.php @@ -42,6 +42,25 @@ protected function getExtendedInterval(): DateInterval { return $this->_cached_extended_interval; } + function setFromData($data): static { + $this->start = $data['start']; + $this->end = $data['end']; + $this->interval = $data['interval']; + return $this; + } + + function ___serialize(): Box|array { + return [ + 'start' => "{$this->start->for_system}", + 'end' => "{$this->end->for_system}", + 'interval' => "{$this->extended_interval->specification_string}", + ]; + } + + protected function ___deserialize(array|Box $data): static { + return $this->setFromData($data); + } + /** * * TODO Add "interval" part to the string diff --git a/src/models/DateTime.php b/src/models/DateTime.php index 9718d54..77e57a1 100644 --- a/src/models/DateTime.php +++ b/src/models/DateTime.php @@ -73,6 +73,31 @@ class DateTime extends FixUpDateTime { // NOTE Is not used anywhere, just a reference for the JSON files public static $l10n_user_default_tz = 'UTC'; + public function __construct( + DateTime|string $datetime = 'now', + null|string|DateTimeZone $timezone = null + ) { + $class_tz = PHP::redef(DateTimeZone::class); + + if ($datetime instanceof DateTime) { + $_tmp = $datetime; + $datetime = $_tmp->for_system; + if (is_null($timezone)) { + $timezone = $_tmp->tz; + } + } + + if (empty($timezone)) { + $timezone = 'UTC'; + } + + if (is_string($timezone)) { + $timezone = new $class_tz($timezone); + } + + parent::__construct($datetime, $timezone); + } + /** * Stores the copy of value before any of "modify", "add" or "sub" performed. * @var static $_orig_value @@ -152,7 +177,7 @@ public function getTimezone(): DateTimeZone|false { * * @return static */ - #[Property('tz')] + #[Property('tz', type: 'set')] #[\ReturnTypeWillChange] public function setTimezone($timezone): static { // IMP This method is original native PHP method, and it expects to return something, @@ -391,4 +416,26 @@ public function toJson(?bool $pretty = null, bool $with_class = false): string { public static function redefComponentName(): string { return InitConfig::REDEF_DATE_TIME; } + + function setFromData($data): static { + $this->__construct(); + $tmp = DT::ts($data['for_system'], $data['tz']); + $this->tz = $tmp->tz; + $this->setTimestamp($tmp->getTimestamp()); + $this->setMicro($tmp->getMicro()); + $tmp = null; + return $this; + } + + function ___serialize(): Box|array { + return [ + 'for_system' => $this->for_system, + 'tz' => Str::ing($this->tz), + ]; + } + + protected function ___deserialize(array|Box $data): static { + $this->setFromData($data); + return $this; + } } diff --git a/src/models/DateTimeZone.php b/src/models/DateTimeZone.php index 9cb222a..afca408 100644 --- a/src/models/DateTimeZone.php +++ b/src/models/DateTimeZone.php @@ -7,6 +7,21 @@ class DateTimeZone extends FixUpDateTimeZone { + function setFromData($data): static { + $this->__construct($data['value']); + return $this; + } + + function ___serialize(): Box|array { + return [ + 'value' => "{$this}", + ]; + } + + protected function ___deserialize(array|Box $data): static { + return $this->setFromData($data); + } + public function __toString(): string { return $this->getName(); // @codeCoverageIgnore } diff --git a/src/models/Dir.php b/src/models/Dir.php index c17c913..de918f3 100644 --- a/src/models/Dir.php +++ b/src/models/Dir.php @@ -198,6 +198,21 @@ public function getIterator() { return new ArrayIterator($this->walk(false)); // @codeCoverageIgnore } + function setFromData($data): static { + $this->__construct($data['value']); + return $this; + } + + function ___serialize(): Box|array { + return [ + 'value' => "{$this->name_full}", + ]; + } + + protected function ___deserialize(array|Box $data): static { + return $this->setFromData($data); + } + public function __toString(): string { return $this->name_full; // @codeCoverageIgnore } diff --git a/src/models/File.php b/src/models/File.php index 2149b41..44f4ba4 100644 --- a/src/models/File.php +++ b/src/models/File.php @@ -25,6 +25,7 @@ use function fstat; use function is_array; use function is_null; +use function md5; use function rewind; use function stat; use function stream_get_contents; @@ -512,6 +513,22 @@ protected function getBackupContent(): ?string { return null; } + function setFromData($data): static { + $this->__construct($data['value']); + return $this; + } + + function ___serialize(): Box|array { + return [ + 'value' => "{$this->name_full}", + 'content_hash' => md5($this->content), + ]; + } + + protected function ___deserialize(array|Box $data): static { + return $this->setFromData($data); + } + //// Some Magic and MetaMagic /** diff --git a/src/models/IPv4.php b/src/models/IPv4.php index df28d14..52faa34 100644 --- a/src/models/IPv4.php +++ b/src/models/IPv4.php @@ -238,6 +238,30 @@ function getData($protocol): ?string { return null; // @codeCoverageIgnore } + function setFromData($data): static { + $this->__construct( + "{$data['value']}/{$data['mask_cidr']}", + $data['output_with_mask'] + ); + return $this; + } + + function ___serialize(): Box|array { + $owm = $this->output_with_mask; + $this->output_with_mask = false; + $res = [ + 'value' => "{$this}", + 'mask_cidr' => "{$this->mask_cidr}", + 'output_with_mask' => $owm + ]; + $this->output_with_mask = $owm; + return $res; + } + + protected function ___deserialize(array|Box $data): static { + return $this->setFromData($data); + } + /** * @inheritDoc */ diff --git a/src/models/IPv4Range.php b/src/models/IPv4Range.php index efe0a3c..5760e77 100644 --- a/src/models/IPv4Range.php +++ b/src/models/IPv4Range.php @@ -42,6 +42,22 @@ function __construct(string|IPv4 $ip1, string|IPv4 $ip2) { } } + function setFromData($data): static { + $this->__construct($data['start'], $data['end']); + return $this; + } + + function ___serialize(): Box|array { + return [ + 'start' => "{$this->start}", + 'end' => "{$this->end}", + ]; + } + + protected function ___deserialize(array|Box $data): static { + return $this->setFromData($data); + } + public function __toString(): string { return "{$this->_start} - {$this->_end}"; } diff --git a/src/models/L10n.php b/src/models/L10n.php index 6e81821..333f5ed 100644 --- a/src/models/L10n.php +++ b/src/models/L10n.php @@ -80,6 +80,25 @@ public function doSetUp() { PHP::metaMagicSpell($class, 'l10n', $this->DataUnit); } + function setFromData($data): static { + $this->name = $data['name']; + $this->DateTime = $data['DateTime']; + $this->DataUnit = $data['DataUnit']; + return $this; + } + + function ___serialize(): Box|array { + return [ + 'name' => $this->name, + 'DateTime' => $this->DateTime, + 'DataUnit' => $this->DataUnit, + ]; + } + + protected function ___deserialize(array|Box $data): static { + return $this->setFromData($data); + } + public static function redefComponentName(): string { return InitConfig::REDEF_L10N; // @codeCoverageIgnore } diff --git a/src/models/Time.php b/src/models/Time.php index 75589d8..73f78d1 100644 --- a/src/models/Time.php +++ b/src/models/Time.php @@ -3,8 +3,8 @@ namespace spaf\simputils\models; use spaf\simputils\attributes\Property; -use spaf\simputils\DT; use spaf\simputils\generic\fixups\FixUpDateTimePrism; +use spaf\simputils\Str; /** * Date Prism @@ -14,12 +14,18 @@ * * @property-read string $for_system * @property-read string $for_user + * @property int $hour Hours + * @property int $minute Minutes + * @property int $second Seconds + * + * @property-read int $milli Milliseconds, at most 3 digits + * @property int $micro Microseconds at most 6 digits */ class Time extends FixUpDateTimePrism { #[Property('for_system')] protected function getForSystem(): string { - return $this->_object->getForSystemObj()->format(DT::FMT_TIME); + return $this->_object->getForSystemObj()->for_system; } #[Property('for_user')] @@ -27,4 +33,65 @@ protected function getForUser(): string { $obj = $this->_object; return $obj->format(DateTime::$l10n_user_time_format); } + + #[Property('hour')] + protected function getHour(): int { + return $this->_object->hour; + } + + #[Property('hour')] + protected function setHour(int $hour): void { + $this->_object->setTime($hour, $this->minute, $this->second, $this->micro); + } + + #[Property('minute')] + protected function getMinute(): int { + return $this->_object->minute; + } + + #[Property('minute')] + protected function setMinute(int $minute): void { + $this->_object->setTime($this->hour, $minute, $this->second, $this->micro); + } + + #[Property('second')] + protected function getSecond(): int { + return $this->_object->second; + } + + #[Property('second')] + protected function setSecond(int $second): void { + $this->_object->setTime($this->hour, $this->minute, $second, $this->micro); + } + + #[Property('micro')] + protected function getMicro(): int { + return $this->_object->micro; + } + + #[Property('micro')] + protected function setMicro(int $micro): void { + $this->_object->setTime($this->hour, $this->minute, $this->second, $micro); + } + + #[Property('milli')] + protected function getMilli(): int { + return $this->_object->milli; + } + + function setFromData($data): static { + $this->__construct($data['for_system'], $data['tz']); + return $this; + } + + function ___serialize(): Box|array { + return [ + 'for_system' => $this->for_system, + 'tz' => Str::ing($this->tz), + ]; + } + + protected function ___deserialize(array|Box $data): static { + return $this->setFromData($data); + } } diff --git a/src/models/UrlObject.php b/src/models/UrlObject.php index 010b000..8a1ba98 100644 --- a/src/models/UrlObject.php +++ b/src/models/UrlObject.php @@ -11,6 +11,7 @@ use spaf\simputils\interfaces\UrlCompatible; use spaf\simputils\models\urls\processors\HttpProtocolProcessor; use spaf\simputils\PHP; +use spaf\simputils\Str; use spaf\simputils\traits\ForOutputsTrait; use spaf\simputils\traits\RedefinableComponentTrait; use function is_array; @@ -253,6 +254,22 @@ protected function getRelative(): string { ); } + function setFromData($data): static { + $this->__construct($data['for_system']); + return $this; + } + + function ___serialize(): Box|array { + return [ + 'for_system' => Str::ing($this->for_system), + ]; + } + + protected function ___deserialize(array|Box $data): static { + $this->setFromData($data); + return $this; + } + /** * @inheritDoc */ diff --git a/src/models/Version.php b/src/models/Version.php index 88900b6..ebb5827 100644 --- a/src/models/Version.php +++ b/src/models/Version.php @@ -9,7 +9,9 @@ use spaf\simputils\components\versions\parsers\DefaultVersionParser; use spaf\simputils\generic\SimpleObject; use spaf\simputils\interfaces\VersionParserInterface; +use spaf\simputils\Str; use spaf\simputils\traits\RedefinableComponentTrait; +use function is_null; use function json_encode; /** @@ -358,6 +360,25 @@ public function toJson(?bool $pretty = null, bool $with_class = false): string { return json_encode("{$this}"); } + function setFromData($data): static { + $this->__construct($data['value'], $data['software']); + return $this; + } + + function ___serialize(): Box|array { + return [ + 'value' => Str::ing($this), + 'software' => !is_null($this->software_name) + ?Str::ing($this->software_name) + :null, + ]; + } + + protected function ___deserialize(array|Box $data): static { + $this->setFromData($data); + return $this; + } + /** * Object representation for debug purposes * diff --git a/tests/general/DateTimeTest.php b/tests/general/DateTimeTest.php index 31788bb..bae170e 100644 --- a/tests/general/DateTimeTest.php +++ b/tests/general/DateTimeTest.php @@ -45,6 +45,7 @@ * @uses \spaf\simputils\models\files\apps\JsonProcessor * @uses \spaf\simputils\models\files\apps\TextProcessor * @uses \spaf\simputils\models\files\apps\settings\DotEnvSettings + * */ class DateTimeTest extends TestCase { @@ -227,16 +228,23 @@ function testDateAndTimePrisms() { $dt = DT::ts('2022-03-31 00:47:58.123456', 'UTC'); // Simple check - $this->assertEquals('2022-03-31', $dt->date->for_system); - $this->assertEquals('00:47:58', $dt->time->for_system); + $this->assertEquals('2022-03-31 00:47:58.123456', $dt->date->for_system); + $this->assertEquals('2022-03-31 00:47:58.123456', $dt->time->for_system); // Modifying the main object, results are seen on the prisms $dt->add('-200 minutes'); - $this->assertEquals('2022-03-30', $dt->date->for_system); - $this->assertEquals('21:27:58', $dt->time->for_system); + $this->assertEquals('2022-03-30 21:27:58.123456', $dt->date->for_system); + $this->assertEquals('2022-03-30 21:27:58.123456', $dt->time->for_system); } + /** + * @runInSeparateProcess + * @return void + */ function testOther() { + PHP::init([ + 'l10n' => 'AT' + ]); $tz_default = DT::getDefaultTimeZone(); $dt = DT::ts('2022-12-25 05:04:03'); @@ -255,7 +263,8 @@ function testOther() { $dt->getForSystemObj()->format(DateTime::$l10n_user_datetime_format) ); - $dt = DT::ts('2022-12-25 05:04:03', true); + $dt = DT::ts('2022-12-25 05:04:03', null); +// pd($dt); $this->assertNotEquals( $dt->for_user, $dt->getForSystemObj()->format(DateTime::$l10n_user_datetime_format) diff --git a/tests/general/ExecEnvsTest.php b/tests/general/ExecEnvsTest.php new file mode 100644 index 0000000..033780d --- /dev/null +++ b/tests/general/ExecEnvsTest.php @@ -0,0 +1,151 @@ +ee = $ee; + $ic->ee->is_hierarchical = $is_hierarchical; + + $this->assertEquals($expected_bool, $ic->ee->is($check_ee)); + } + +} diff --git a/tests/general/NewFeature202207Test.php b/tests/general/NewFeature202207Test.php new file mode 100644 index 0000000..64e1489 --- /dev/null +++ b/tests/general/NewFeature202207Test.php @@ -0,0 +1,49 @@ + 'AT', + ]); + } + + /** + * @covers \spaf\simputils\models\Box + * @return void + */ + function testBoxBatchFunctionality() { + $a = bx([ + 'key 1' => 'BIG TEXT', + 'key 2' => 22.22, + 'key 3' => 100_500, + 'key 4' => new Version('1.2.3 DEV', 'SPEC APP'), + '99 Luftballons' => 'hehehe', + ]); + + // NOTE Case 1 + [$k1, $k2, $k3, $k4] = $a->batch(['key 1', 'key 2', 'key 3', 'key 4'], true); + + $this->assertEquals($a['key 1'], $k1); + $this->assertEquals($a['key 2'], $k2); + $this->assertEquals($a['key 3'], $k3); + $this->assertEquals($a['key 4'], $k4); + + // NOTE Case 2 + extract($a->batch(['key 1', 'key 3', '99 Luftballons'])); + + $this->assertEquals('BIG TEXT', $key_1); + $this->assertEquals(100_500, $key_3); + $this->assertEquals('hehehe', $_99_Luftballons); + } + +} diff --git a/tests/general/PrismsTest.php b/tests/general/PrismsTest.php index 601a454..1b30a99 100644 --- a/tests/general/PrismsTest.php +++ b/tests/general/PrismsTest.php @@ -33,8 +33,8 @@ function testBasics() { $prism_d = $dt->date; $prism_t = $dt->time; - $this->assertEquals('2020-03-04', "{$prism_d->for_system}"); - $this->assertEquals('05:06:07', "{$prism_t->for_system}"); + $this->assertEquals('2020-03-04 05:06:07.000000', "{$prism_d->for_system}"); + $this->assertEquals('2020-03-04 05:06:07.000000', "{$prism_t->for_system}"); $this->assertEquals($dt, $prism_d->object); $this->assertEquals($dt, $prism_t->object); @@ -48,7 +48,7 @@ function testBasics() { $this->assertInstanceOf(Date::class, $prism_d2); $this->assertInstanceOf(Time::class, $prism_t2); - $this->assertEquals('2020-03-04', "{$prism_d2->for_system}"); - $this->assertEquals('05:06:07', "{$prism_t2->for_system}"); + $this->assertEquals('2020-03-04 05:06:07.000000', "{$prism_d2->for_system}"); + $this->assertEquals('2020-03-04 05:06:07.000000', "{$prism_t2->for_system}"); } }