From 41d07fcc82a98acb4f743c83c351a073263a6316 Mon Sep 17 00:00:00 2001 From: Maxim Akimov Date: Fri, 20 Dec 2024 23:25:07 +0200 Subject: [PATCH 1/4] tests --- tests/Unit/TypedTest.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/Unit/TypedTest.php b/tests/Unit/TypedTest.php index 356bd48..d5f45f5 100644 --- a/tests/Unit/TypedTest.php +++ b/tests/Unit/TypedTest.php @@ -370,19 +370,22 @@ public function testAnyMethodReturnsDefaultForMissingKeyInMixedStructuresWhenPas // functions.php - public function testFunctionsFileSkipsDeclarationsByDefault() + public function testFunctionsFileSkipsDeclarationsByDefault(): void { include __DIR__ . '/../../src/functions.php'; $this->assertFalse(function_exists('string')); } - public function testFunctionsFileDeclaretsFunctionsWithConstant() + public function testFunctionsFileDeclaretsFunctionsWithConstant(): void { define('WPLAKE_TYPED_FUNCTIONS', true); include __DIR__ . '/../../src/functions.php'; - $this->assertTrue(function_exists('string')); + $array = ['key' => 'value']; + $result = string($array, 'key'); + + $this->assertSame('value', $result); } } From 884580f26a2cc2627f4b6a985b51692cd2b9af2d Mon Sep 17 00:00:00 2001 From: Maxim Akimov Date: Fri, 20 Dec 2024 23:27:42 +0200 Subject: [PATCH 2/4] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 978ae23..bbca0e5 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ OOP is indeed powerful, and you should always prioritize using objects whenever our code often interacts with external dependencies beyond our control. This package simplifies handling such scenarios. -Any seasoned PHP developer knows the pain of type-casting when working with environments outside of frameworks like +Any seasoned PHP developer knows the pain of type-casting when working with environments outside of frameworks, e.g. in WordPress. ### 6.4) Is the dot syntax in keys inspired by Laravel Collections? From 95d63565c73701911773f318e313a7bcbefe8b89 Mon Sep 17 00:00:00 2001 From: Maxim Akimov Date: Fri, 20 Dec 2024 23:31:14 +0200 Subject: [PATCH 3/4] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bbca0e5..397d6dd 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ define('WPLAKE_TYPED_FUNCTIONS', true); require __DIR__ . '/vendor/autoload.php'; ``` -Once enabled, you can enjoy clean and intuitive syntax for the all types, with the added safety and flexibility. +Once enabled, you can enjoy clean and intuitive syntax and call all the functions listed above without the `Typed::` prefix, just as `string()`, `float()`, and so on. Note: Unlike all the other types, the `array` keyword falls under a [different category](https://www.php.net/manual/en/reserved.keywords.php), which also prohibits its usage for function From ebf07370558a78b37b649ec8720a2e6ddace5480 Mon Sep 17 00:00:00 2001 From: Maxim Akimov Date: Sat, 21 Dec 2024 00:10:35 +0200 Subject: [PATCH 4/4] functions --- README.md | 115 +++++------ src/functions.php | 404 +++++++++++++++++---------------------- tests/Unit/TypedTest.php | 15 +- 3 files changed, 240 insertions(+), 294 deletions(-) diff --git a/README.md b/README.md index 397d6dd..9b0a428 100644 --- a/README.md +++ b/README.md @@ -33,27 +33,44 @@ function getTypedStringFromMixedVariable($mixed): string } ``` -**The same with `Typed` utility** +**The same with the `Typed` utility** ```php -use WPLake\Typed\Typed; -use WPLake\Typed\Typed; +use function WPLake\Typed\int; +use function WPLake\Typed\string; function getTypedIntFromArray(array $data): int { - return Typed::int($data, 'meta.number'); + return int($data, 'meta.number'); } function getTypedStringFromMixedVariable($mixedData): string { - return Typed::string($mixedData); + return string($mixedData); } ``` -Want to provide a default value when the key is missing? Here you go: +The code like `string($array, 'key')` resembles `(string)$array['key']` while being +safe and smart — it even handles nested keys. + +> In case now you're thinking: "Hold on guys, but this code won't work! Are your using type names as function names?" +> +> Our answer is: "Yes! And actually it isn't prohibited." +> +> See the explanation in the special section - [5. Note about the function names](#5-note-about-the-function-names) + +Backing to the package. Want to provide a default value when the key is missing? Here you go: + +```php +string($data, 'some.key', 'Default Value'); +``` + +Don't like functions? The same functions set is available as static methods of the `Typed` class: ```php -Typed::string($data, 'some.key', 'Default Value'); +use WPLake\Typed\Typed; + +Typed::int($data,'key'); ``` ## 2. Installation and usage @@ -69,30 +86,33 @@ After installation, ensure that your application includes the Composer autoloade Usage: ```php +use function WPLake\Typed\string; use WPLake\Typed\Typed; +$string = string($array, 'first.second','default value'); +// alternatively: $string = Typed::string($array, 'first.second','default value'); ``` ## 3. Supported types -Static methods for the following types are present: +Functions for the following types are present: -* `Typed::string` -* `Typed::int` -* `Typed::float` -* `Typed::bool` -* `Typed::array` -* `Typed::object` -* `Typed::dateTime` -* `Typed::any` (allows to use short dot-keys usage for unknowns) +* `string` +* `int` +* `float` +* `bool` +* `array` +* `object` +* `dateTime` +* `any` (allows to use short dot-keys usage for unknowns) Additionally: -* `Typed::boolExtended` (`true`,`1`,`"1"`, `"on"` are treated as true, `false`,`0`,`"0"`, `"off"` as false) -* `Typed::stringExtended` (supports objects with `__toString`) +* `boolExtended` (`true`,`1`,`"1"`, `"on"` are treated as true, `false`,`0`,`"0"`, `"off"` as false) +* `stringExtended` (supports objects with `__toString`) -For optional cases, each item has an `OrNull` method option (e.g. `Typed::stringOrNull`, `Typed::intOrNull`, and so on), +For optional cases, each item has an `OrNull` method option (e.g. `stringOrNull`, `intOrNull`, and so on), which returns `null` if the key doesn’t exist. ## 4. How It Works @@ -106,15 +126,11 @@ For example, let's review the `string` method declaration: ```php namespace WPLake\Typed; -class Typed { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - public static function string($source, $keys = null, string $default = ''): string; - - // ... -} +/** + * @param mixed $source + * @param int|string|array|null $keys + */ +function string($source, $keys = null, string $default = ''): string; ``` Usage Scenarios: @@ -122,71 +138,56 @@ Usage Scenarios: 1. Extract a string from a mixed variable (returning the default if absent or of an incompatible type) ```php -$userName = Typed::string($unknownVar); +$userName = string($unknownVar); ``` 2. Retrieve a string from an array, including nested structures (with dot notation or as an array). ```php -$userName = Typed::string($array, 'user.name'); +$userName = string($array, 'user.name'); // alternatively: -$userName = Typed::string($array, ['user','name',]); +$userName = string($array, ['user','name',]); ``` 3. Access a string from an object. It also supports the nested properties. ```php -$userName = Typed::string($companyObject, 'user.name'); +$userName = string($companyObject, 'user.name'); // alternatively: -$userName = Typed::string($companyObject, ['user', 'name',]); +$userName = string($companyObject, ['user', 'name',]); ``` 4. Work with mixed structures (e.g., `object->arrayProperty['key']->anotherProperty or ['key' => $object]`). ```php -$userName = Typed::string($companyObject,'users.john.name'); +$userName = string($companyObject,'users.john.name'); // alternatively: -$userName = Typed::string($companyObject,['users','john','name',]); +$userName = string($companyObject,['users','john','name',]); ``` In all the cases, you can pass a default value as the third argument, e.g.: ```php -$userName = Typed::string($companyObject,'users.john.name', 'Guest'); +$userName = string($companyObject,'users.john.name', 'Guest'); ``` -## 5. Global Helper Functions +## 5. Note about the function names -Surprisingly, PHP allows global functions to share the same names as variable types. +Surprisingly, PHP allows functions to use the same names as variable types. Think it’s prohibited? Not quite! While certain names are restricted for classes, interfaces, and traits, function names are not: -> “These names cannot be used to name a class, interface, or -> trait” - [PHP Manual: Reserved Other Reserved Words](https://www.php.net/manual/en/reserved.other-reserved-words.php) +> “These names cannot be used to name a **class, interface, or +> trait**” - [PHP Manual: Reserved Other Reserved Words](https://www.php.net/manual/en/reserved.other-reserved-words.php) -This means you can have something like `string($array, 'key')`, which resembles `(string)$array['key']` while being +This means you we can have things like `string($array, 'key')`, which resembles `(string)$array['key']` while being safer and smarter — it even handles nested keys. -However, since these functions must be declared in the global namespace (you can’t use `WPLake\Typed\string`), their -usage is optional. - -**How to Enable** - -To enable these global helper functions, define the following constant before including the Composer autoloader: - -```php -define('WPLAKE_TYPED_FUNCTIONS', true); - -require __DIR__ . '/vendor/autoload.php'; -``` - -Once enabled, you can enjoy clean and intuitive syntax and call all the functions listed above without the `Typed::` prefix, just as `string()`, `float()`, and so on. - Note: Unlike all the other types, the `array` keyword falls under a [different category](https://www.php.net/manual/en/reserved.keywords.php), which also prohibits its usage for function -names. That's why in this case we used the `arr` instead. +names. That's why in this case we used the `arr` name instead. ## 6. FAQ diff --git a/src/functions.php b/src/functions.php index 7edc34f..d4a4020 100644 --- a/src/functions.php +++ b/src/functions.php @@ -2,7 +2,9 @@ declare(strict_types=1); -use WPLake\Typed\Typed; +namespace WPLake\Typed; + +use DateTime; // Ready to break some rules? Here's the deal: // In PHP, you can declare functions with names matching variable types. @@ -11,246 +13,198 @@ // “These names cannot be used to name a class, interface, or trait” – // https://www.php.net/manual/en/reserved.other-reserved-words.php -// Since these functions must be declared in the global namespace (you can't go with 'use WPLake\\Typed\\string'), -// we've made their usage optional. - -if ( - true === defined('WPLAKE_TYPED_FUNCTIONS') && - true === constant('WPLAKE_TYPED_FUNCTIONS') -) { - // every function is wrapped with its own check to allow you override it. - - if (false === function_exists('any')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - * @param mixed $default - * - * @return mixed - */ - function any($source, $keys = null, $default = null) - { - return Typed::any($source, $keys, $default); - } - } +/** + * @param mixed $source + * @param int|string|array|null $keys + * @param mixed $default + * + * @return mixed + */ +function any($source, $keys = null, $default = null) +{ + return Typed::any($source, $keys, $default); +} - if (false === function_exists('string')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function string($source, $keys = null, string $default = ''): string - { - return Typed::string($source, $keys, $default); - } - } +/** + * @param mixed $source + * @param int|string|array|null $keys + */ +function string($source, $keys = null, string $default = ''): string +{ + return Typed::string($source, $keys, $default); +} - if (false === function_exists('stringExtended')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function stringExtended($source, $keys = null, string $default = ''): string - { - return Typed::stringExtended($source, $keys, $default); - } - } +/** + * @param mixed $source + * @param int|string|array|null $keys + */ +function stringExtended($source, $keys = null, string $default = ''): string +{ + return Typed::stringExtended($source, $keys, $default); +} - if (false === function_exists('stringOrNull')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function stringOrNull($source, $keys = null): ?string - { - return Typed::stringOrNull($source, $keys); - } - } +/** + * @param mixed $source + * @param int|string|array|null $keys + */ +function stringOrNull($source, $keys = null): ?string +{ + return Typed::stringOrNull($source, $keys); +} - if (false === function_exists('stringExtendedOrNull')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function stringExtendedOrNull($source, $keys = null): ?string - { - return Typed::stringExtendedOrNull($source, $keys); - } - } +/** + * @param mixed $source + * @param int|string|array|null $keys + */ +function stringExtendedOrNull($source, $keys = null): ?string +{ + return Typed::stringExtendedOrNull($source, $keys); +} - if (false === function_exists('int')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function int($source, $keys = null, int $default = 0): int - { - return Typed::int($source, $keys, $default); - } - } +/** + * @param mixed $source + * @param int|string|array|null $keys + */ +function int($source, $keys = null, int $default = 0): int +{ + return Typed::int($source, $keys, $default); +} - if (false === function_exists('intOrNull')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function intOrNull($source, $keys = null): ?int - { - return Typed::intOrNull($source, $keys); - } - } +/** + * @param mixed $source + * @param int|string|array|null $keys + */ +function intOrNull($source, $keys = null): ?int +{ + return Typed::intOrNull($source, $keys); +} - if (false === function_exists('float')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function float($source, $keys = null, float $default = 0.0): float - { - return Typed::float($source, $keys, $default); - } - } +/** + * @param mixed $source + * @param int|string|array|null $keys + */ +function float($source, $keys = null, float $default = 0.0): float +{ + return Typed::float($source, $keys, $default); +} - if (false === function_exists('floatOrNull')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function floatOrNull($source, $keys = null): ?float - { - return Typed::floatOrNull($source, $keys); - } - } +/** + * @param mixed $source + * @param int|string|array|null $keys + */ +function floatOrNull($source, $keys = null): ?float +{ + return Typed::floatOrNull($source, $keys); +} - if (false === function_exists('bool')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function bool($source, $keys = null, bool $default = false): bool - { - return Typed::bool($source, $keys, $default); - } - } +/** + * @param mixed $source + * @param int|string|array|null $keys + */ +function bool($source, $keys = null, bool $default = false): bool +{ + return Typed::bool($source, $keys, $default); +} - if (false === function_exists('boolOrNull')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function boolOrNull($source, $keys = null): ?bool - { - return Typed::boolOrNull($source, $keys); - } - } +/** + * @param mixed $source + * @param int|string|array|null $keys + */ +function boolOrNull($source, $keys = null): ?bool +{ + return Typed::boolOrNull($source, $keys); +} - if (false === function_exists('boolExtended')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - * @param array $positive - * @param array $negative - */ - function boolExtended( - $source, - $keys = null, - bool $default = false, - array $positive = [true, 1, '1', 'on',], - array $negative = [false, 0, '0', 'off',] - ): bool { - return Typed::boolExtended($source, $keys, $default, $positive, $negative); - } - } +/** + * @param mixed $source + * @param int|string|array|null $keys + * @param array $positive + * @param array $negative + */ +function boolExtended( + $source, + $keys = null, + bool $default = false, + array $positive = [true, 1, '1', 'on',], + array $negative = [false, 0, '0', 'off',] +): bool { + return Typed::boolExtended($source, $keys, $default, $positive, $negative); +} - if (false === function_exists('boolExtendedOrNull')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - * @param array $positive - * @param array $negative - */ - function boolExtendedOrNull( - $source, - $keys = null, - array $positive = [true, 1, '1', 'on',], - array $negative = [false, 0, '0', 'off',] - ): ?bool { - return Typed::boolExtendedOrNull($source, $keys, $positive, $negative); - } - } +/** + * @param mixed $source + * @param int|string|array|null $keys + * @param array $positive + * @param array $negative + */ +function boolExtendedOrNull( + $source, + $keys = null, + array $positive = [true, 1, '1', 'on',], + array $negative = [false, 0, '0', 'off',] +): ?bool { + return Typed::boolExtendedOrNull($source, $keys, $positive, $negative); +} - if (false === function_exists('arr')) { - /** - * Unlike other types, the 'array' keyword falls under a different category, - * which also prohibits its usage for function names – https://www.php.net/manual/en/reserved.keywords.php - * That's why we'll stick to using 'arr' instead. - * - * @param mixed $source - * @param int|string|array|null $keys - * @param array $default - * - * @return array - */ - function arr($source, $keys = null, array $default = []): array - { - return Typed::array($source, $keys, $default); - } - } +/** + * Unlike other types, the 'array' keyword falls under a different category, + * which also prohibits its usage for function names – https://www.php.net/manual/en/reserved.keywords.php + * That's why we'll stick to using 'arr' instead. + * + * @param mixed $source + * @param int|string|array|null $keys + * @param array $default + * + * @return array + */ +function arr($source, $keys = null, array $default = []): array +{ + return Typed::array($source, $keys, $default); +} - if (false === function_exists('arrayOrNull')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - * - * @return array|null - */ - function arrayOrNull($source, $keys = null): ?array - { - return Typed::arrayOrNull($source, $keys); - } - } +/** +* @param mixed $source +* @param int|string|array|null $keys +* +* @return array|null +*/ +function arrayOrNull($source, $keys = null): ?array +{ + return Typed::arrayOrNull($source, $keys); +} - if (false === function_exists('object')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function object($source, $keys = null, ?object $default = null): object - { - return Typed::object($source, $keys, $default); - } - } +/** +* @param mixed $source +* @param int|string|array|null $keys +*/ +function object($source, $keys = null, ?object $default = null): object +{ + return Typed::object($source, $keys, $default); +} - if (false === function_exists('objectOrNull')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function objectOrNull($source, $keys = null): ?object - { - return Typed::objectOrNull($source, $keys); - } - } +/** +* @param mixed $source +* @param int|string|array|null $keys +*/ +function objectOrNull($source, $keys = null): ?object +{ + return Typed::objectOrNull($source, $keys); +} - if (false === function_exists('dateTime')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function dateTime($source, $keys = null, ?DateTime $default = null): DateTime - { - return Typed::dateTime($source, $keys, $default); - } - } +/** +* @param mixed $source +* @param int|string|array|null $keys +*/ +function dateTime($source, $keys = null, ?DateTime $default = null): DateTime +{ + return Typed::dateTime($source, $keys, $default); +} - if (false === function_exists('dateTimeOrNull')) { - /** - * @param mixed $source - * @param int|string|array|null $keys - */ - function dateTimeOrNull($source, $keys = null): ?DateTime - { - return Typed::dateTimeOrNull($source, $keys); - } - } +/** +* @param mixed $source +* @param int|string|array|null $keys + */ +function dateTimeOrNull($source, $keys = null): ?DateTime +{ + return Typed::dateTimeOrNull($source, $keys); } diff --git a/tests/Unit/TypedTest.php b/tests/Unit/TypedTest.php index d5f45f5..c346aa7 100644 --- a/tests/Unit/TypedTest.php +++ b/tests/Unit/TypedTest.php @@ -6,6 +6,8 @@ use stdClass; use WPLake\Typed\Typed; +use function WPLake\Typed\string; + class TypedTest extends TestCase { // Note: All the methods are decorators for the 'any()' method, so we can focus solely on it. @@ -370,19 +372,8 @@ public function testAnyMethodReturnsDefaultForMissingKeyInMixedStructuresWhenPas // functions.php - public function testFunctionsFileSkipsDeclarationsByDefault(): void + public function testFunctionsAreAvailable(): void { - include __DIR__ . '/../../src/functions.php'; - - $this->assertFalse(function_exists('string')); - } - - public function testFunctionsFileDeclaretsFunctionsWithConstant(): void - { - define('WPLAKE_TYPED_FUNCTIONS', true); - - include __DIR__ . '/../../src/functions.php'; - $array = ['key' => 'value']; $result = string($array, 'key');