From 956294dcb7374a1d5a00673e96de9d1c8a6ca545 Mon Sep 17 00:00:00 2001 From: mambax7 Date: Thu, 18 May 2023 23:28:42 -0400 Subject: [PATCH 01/19] adding Ulid --- src/Ulid.php | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/Ulid.php diff --git a/src/Ulid.php b/src/Ulid.php new file mode 100644 index 0000000..32b08e6 --- /dev/null +++ b/src/Ulid.php @@ -0,0 +1,60 @@ + + * @copyright 2023 XOOPS Project (https://xoops.org) + * @license GNU GPL 2 or later (https://www.gnu.org/licenses/gpl-2.0.html) + */ +class Ulid +{ + /** + * Generate a new ULID. + * + * @return string The generated ULID. + * @throws \Exception + */ + public static function generate() + { + $time = microtime(true) * 1000; + $timestamp = sprintf('%012x', (int)($time)); + $randomness = self::generateRandomness(true); + + return $timestamp . $randomness; + } + + /** + * Generate a random 80-bit randomness component for the ULID. + * + * @param bool $strongAlgorithm Determines if the algorithm used should be cryptographically strong. + * @return string The generated randomness component. + * @throws \Exception + */ + private static function generateRandomness(bool $strongAlgorithm): string + { + if ($strongAlgorithm && function_exists('random_bytes')) { + $bytes = random_bytes(10); + } else { + $bytes = ''; + for ($i = 0; $i < 10; $i++) { + $bytes .= chr(random_int(0, 255)); + } + } + + return bin2hex($bytes); + } +} From 53a5c2cfa3917acfffaa3c10e2f11e32c449b0f4 Mon Sep 17 00:00:00 2001 From: mambax7 Date: Thu, 18 May 2023 23:36:52 -0400 Subject: [PATCH 02/19] adding UlidTest --- tests/unit/UlidTest.php | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/unit/UlidTest.php diff --git a/tests/unit/UlidTest.php b/tests/unit/UlidTest.php new file mode 100644 index 0000000..a4d2423 --- /dev/null +++ b/tests/unit/UlidTest.php @@ -0,0 +1,34 @@ +assertRegExp('/^[0-9a-f]{26}$/', $ulid); + } +} + From 2fafefb45797cc5c2c16ea08dc7a9a47ec05ead8 Mon Sep 17 00:00:00 2001 From: mambax7 Date: Fri, 19 May 2023 06:08:50 -0400 Subject: [PATCH 03/19] Fixes based on unit tests --- src/Ulid.php | 49 ++++++++++++++++++++++++++--------------- tests/unit/UlidTest.php | 2 +- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/Ulid.php b/src/Ulid.php index 32b08e6..c7bc612 100644 --- a/src/Ulid.php +++ b/src/Ulid.php @@ -22,39 +22,52 @@ */ class Ulid { + const ENCODING_CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; + const ENCODING_LENGTH = 32; /** * Generate a new ULID. * * @return string The generated ULID. * @throws \Exception */ - public static function generate() + + public static function generate(bool $upperCase = true): string { - $time = microtime(true) * 1000; - $timestamp = sprintf('%012x', (int)($time)); - $randomness = self::generateRandomness(true); + $time = (int)(microtime(true) * 1000); + $timeChars = self::encodeTime($time); + $randChars = self::encodeRandomness(); + $ulid = $timeChars . $randChars; - return $timestamp . $randomness; + return $upperCase ? strtoupper($ulid) : strtolower($ulid); + } + + private static function encodeTime(int $time): string + { + $timeChars = ''; + for ($i = 0; $i < 10; $i++) { + $mod = $time % self::ENCODING_LENGTH; + $timeChars = self::ENCODING_CHARS[$mod] . $timeChars; + $time = ($time - $mod) / self::ENCODING_LENGTH; + } + return $timeChars; } /** - * Generate a random 80-bit randomness component for the ULID. - * - * @param bool $strongAlgorithm Determines if the algorithm used should be cryptographically strong. - * @return string The generated randomness component. * @throws \Exception */ - private static function generateRandomness(bool $strongAlgorithm): string + private static function encodeRandomness(): string { - if ($strongAlgorithm && function_exists('random_bytes')) { - $bytes = random_bytes(10); - } else { - $bytes = ''; - for ($i = 0; $i < 10; $i++) { - $bytes .= chr(random_int(0, 255)); + $randomBytes = random_bytes(10); // 80 bits + $randChars = ''; + for ($i = 0; $i < 16; $i++) { + $randValue = ord($randomBytes[$i % 10]); + if ($i % 2 === 0) { + $randValue >>= 3; // take the upper 5 bits + } else { + $randValue &= 31; // take the lower 5 bits } + $randChars .= self::ENCODING_CHARS[$randValue]; } - - return bin2hex($bytes); + return $randChars; } } diff --git a/tests/unit/UlidTest.php b/tests/unit/UlidTest.php index a4d2423..8be12aa 100644 --- a/tests/unit/UlidTest.php +++ b/tests/unit/UlidTest.php @@ -28,7 +28,7 @@ public function testGenerate() { $ulid = Ulid::generate(); - $this->assertRegExp('/^[0-9a-f]{26}$/', $ulid); + $this->assertMatchesRegularExpression('/^[0-9A-Z]{26}$/', \strtoupper($ulid)); } } From 02712c88848dd40654bb86feab07c45f5c9378be Mon Sep 17 00:00:00 2001 From: mambax7 Date: Fri, 19 May 2023 06:13:39 -0400 Subject: [PATCH 04/19] using assertRegExp() --- tests/unit/UlidTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/UlidTest.php b/tests/unit/UlidTest.php index 8be12aa..f21836b 100644 --- a/tests/unit/UlidTest.php +++ b/tests/unit/UlidTest.php @@ -28,7 +28,7 @@ public function testGenerate() { $ulid = Ulid::generate(); - $this->assertMatchesRegularExpression('/^[0-9A-Z]{26}$/', \strtoupper($ulid)); + $this->assertRegExp('/^[0-9A-Z]{26}$/', \strtoupper($ulid)); } } From fa21928987100945402236356573b15eb76a7622 Mon Sep 17 00:00:00 2001 From: mambax7 Date: Fri, 19 May 2023 06:21:02 -0400 Subject: [PATCH 05/19] typo --- src/Ulid.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ulid.php b/src/Ulid.php index c7bc612..4f62417 100644 --- a/src/Ulid.php +++ b/src/Ulid.php @@ -12,7 +12,7 @@ namespace Xmf; /** - * Generate UUID + * Generate ULID * * @category Xmf\Ulid * @package Xmf From 3b8fdf20d3225e37ba22acdbc47f46f144fec88f Mon Sep 17 00:00:00 2001 From: mambax7 Date: Sat, 20 May 2023 14:17:07 -0400 Subject: [PATCH 06/19] Added extra tests --- src/Ulid.php | 15 ++++++++++++++ tests/unit/UlidTest.php | 43 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/Ulid.php b/src/Ulid.php index 4f62417..86ac5fe 100644 --- a/src/Ulid.php +++ b/src/Ulid.php @@ -24,6 +24,12 @@ class Ulid { const ENCODING_CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; const ENCODING_LENGTH = 32; + /** + * Last generated timestamp. + * @var int + */ + private static $lastTime = 0; + /** * Generate a new ULID. * @@ -34,6 +40,15 @@ class Ulid public static function generate(bool $upperCase = true): string { $time = (int)(microtime(true) * 1000); + + // If the current timestamp is equal or less than the last generated one, + // increase it by one millisecond to ensure ULIDs are always sorted correctly. + if ($time <= self::$lastTime) { + $time = self::$lastTime + 1; + } + + self::$lastTime = $time; + $timeChars = self::encodeTime($time); $randChars = self::encodeRandomness(); $ulid = $timeChars . $randChars; diff --git a/tests/unit/UlidTest.php b/tests/unit/UlidTest.php index f21836b..631a5e2 100644 --- a/tests/unit/UlidTest.php +++ b/tests/unit/UlidTest.php @@ -30,5 +30,48 @@ public function testGenerate() $this->assertRegExp('/^[0-9A-Z]{26}$/', \strtoupper($ulid)); } + public function testGeneratesLowercaseIdentifierWhenConfigured(): void + { + $ulid = Ulid::generate(false); //generate lower case + + $this->assertMatchesRegularExpression('/[0-9][a-z]/', $ulid); + } + + public function testGeneratesTwentySixChars(): void + { + $this->assertSame(26, strlen(Ulid::generate())); + } + + public function testRandomnessWhenGeneratedMultipleTimes(): void + { + $a = Ulid::generate(); + $b = Ulid::generate(); + + // The time parts are different. + $this->assertNotEquals(substr($a, 0, 10), substr($b, 0, 10)); + + //the second ULID time part is bigger than the first ULID + $this->assertGreaterThan(substr($a, 0, 10), substr($b, 0, 10)); + + // Only the last time character should be different. + $this->assertEquals(substr($a, 0, 9), substr($b, 0, 9)); + + //the random characters part should be different + $this->assertNotEquals(substr($a, 10), substr($b, 10)); + } + + public function testGeneratesLexographicallySortableUlids(): void + { + $a = Ulid::generate(); + + sleep(1); + + $b = Ulid::generate(); + + $ulids = [(string) $b, (string) $a]; + usort($ulids, 'strcmp'); + + $this->assertSame([(string) $a, (string) $b], $ulids); + } } From 18eac068b669c8df01039702d29c5efe30c22283 Mon Sep 17 00:00:00 2001 From: mambax7 Date: Sat, 20 May 2023 14:20:29 -0400 Subject: [PATCH 07/19] downgrade function to assertRegExp() --- tests/unit/UlidTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/UlidTest.php b/tests/unit/UlidTest.php index 631a5e2..7820e81 100644 --- a/tests/unit/UlidTest.php +++ b/tests/unit/UlidTest.php @@ -34,7 +34,7 @@ public function testGeneratesLowercaseIdentifierWhenConfigured(): void { $ulid = Ulid::generate(false); //generate lower case - $this->assertMatchesRegularExpression('/[0-9][a-z]/', $ulid); + $this->assertRegExp('/[0-9][a-z]/', $ulid); } public function testGeneratesTwentySixChars(): void From 88d3911be8677e11df833fc6e4546295ad73731e Mon Sep 17 00:00:00 2001 From: mambax7 Date: Mon, 2 Oct 2023 22:27:09 -0400 Subject: [PATCH 08/19] making it stateless, increasing time precision, replace assertRegExp() with assertMatchesRegularExpression() --- src/Ulid.php | 16 +--------------- tests/unit/UlidTest.php | 13 ++++++++----- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/Ulid.php b/src/Ulid.php index 86ac5fe..0590d8d 100644 --- a/src/Ulid.php +++ b/src/Ulid.php @@ -24,11 +24,6 @@ class Ulid { const ENCODING_CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; const ENCODING_LENGTH = 32; - /** - * Last generated timestamp. - * @var int - */ - private static $lastTime = 0; /** * Generate a new ULID. @@ -39,16 +34,7 @@ class Ulid public static function generate(bool $upperCase = true): string { - $time = (int)(microtime(true) * 1000); - - // If the current timestamp is equal or less than the last generated one, - // increase it by one millisecond to ensure ULIDs are always sorted correctly. - if ($time <= self::$lastTime) { - $time = self::$lastTime + 1; - } - - self::$lastTime = $time; - + $time = (int)(microtime(true) * 1000000); $timeChars = self::encodeTime($time); $randChars = self::encodeRandomness(); $ulid = $timeChars . $randChars; diff --git a/tests/unit/UlidTest.php b/tests/unit/UlidTest.php index 7820e81..7c20ec5 100644 --- a/tests/unit/UlidTest.php +++ b/tests/unit/UlidTest.php @@ -9,7 +9,7 @@ class UlidTest extends \PHPUnit\Framework\TestCase * Sets up the fixture, for example, opens a network connection. * This method is called before a test is executed. */ - protected function setUp() + protected function setUp(): void { } @@ -17,7 +17,7 @@ protected function setUp() * Tears down the fixture, for example, closes a network connection. * This method is called after a test is executed. */ - protected function tearDown() + protected function tearDown(): void { } @@ -26,15 +26,18 @@ protected function tearDown() */ public function testGenerate() { - $ulid = Ulid::generate(); + $ulid1 = Ulid::generate(); + usleep(2000); // Wait for 2 milliseconds to ensure a different timestamp + $ulid2 = Ulid::generate(); - $this->assertRegExp('/^[0-9A-Z]{26}$/', \strtoupper($ulid)); + $this->assertNotEquals($ulid1, $ulid2, 'ULIDs should be unique'); + $this->assertTrue(strcasecmp($ulid1, $ulid2) < 0, 'ULIDs should collate correctly'); } public function testGeneratesLowercaseIdentifierWhenConfigured(): void { $ulid = Ulid::generate(false); //generate lower case - $this->assertRegExp('/[0-9][a-z]/', $ulid); + $this->assertMatchesRegularExpression('/[0-9][a-z]/', $ulid); } public function testGeneratesTwentySixChars(): void From 86abde66ccf323f92b649f6127944b1cfc3cf4b8 Mon Sep 17 00:00:00 2001 From: mambax7 Date: Mon, 30 Oct 2023 11:34:15 -0400 Subject: [PATCH 09/19] cosmetics --- docs/gpl-3.0.txt | 6 +++--- docs/license.md | 2 +- index.php | 2 +- src/Assert.php | 2 +- src/Database/TableLoad.php | 6 +++--- src/Database/Tables.php | 4 ++-- src/Database/index.php | 2 +- src/Debug.php | 2 +- src/Highlighter.php | 2 +- src/IPAddress.php | 2 +- src/Jwt/JsonWebToken.php | 2 +- src/Jwt/KeyFactory.php | 2 +- src/Jwt/TokenFactory.php | 2 +- src/Jwt/TokenReader.php | 2 +- src/Jwt/index.php | 2 +- src/Key/ArrayStorage.php | 2 +- src/Key/Basic.php | 2 +- src/Key/FileStorage.php | 2 +- src/Key/KeyAbstract.php | 2 +- src/Key/StorageInterface.php | 2 +- src/Key/index.php | 2 +- src/Language.php | 2 +- src/Metagen.php | 4 ++-- src/Module/Helper.php | 2 +- src/Module/Helper/AbstractHelper.php | 4 ++-- src/Module/Helper/Cache.php | 2 +- src/Module/Helper/GenericHelper.php | 6 +++--- src/Module/Helper/Permission.php | 4 ++-- src/Module/Helper/Session.php | 2 +- src/Module/Helper/index.php | 2 +- src/Module/index.php | 2 +- src/StopWords.php | 2 +- src/Uuid.php | 4 ++-- src/Yaml.php | 2 +- src/index.php | 2 +- 35 files changed, 46 insertions(+), 46 deletions(-) diff --git a/docs/gpl-3.0.txt b/docs/gpl-3.0.txt index 94a9ed0..2a00065 100644 --- a/docs/gpl-3.0.txt +++ b/docs/gpl-3.0.txt @@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. @@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see -. +. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. diff --git a/docs/license.md b/docs/license.md index 9136add..f48f382 100644 --- a/docs/license.md +++ b/docs/license.md @@ -9,6 +9,6 @@ Some portions of this work are licensed under the GNU Lesser General Public Lice This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -You should have received a copy of the GNU General Public License, and the GNU Lesser General Public License along with this program. If not, see . +You should have received a copy of the GNU General Public License, and the GNU Lesser General Public License along with this program. If not, see . You may contact the copyright holder through XOOPS Project: diff --git a/index.php b/index.php index ea14d56..db4baed 100644 --- a/index.php +++ b/index.php @@ -1,2 +1,2 @@ * @copyright 2017 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class Assert extends BaseAssertion diff --git a/src/Database/TableLoad.php b/src/Database/TableLoad.php index 7c244c4..4ba474f 100644 --- a/src/Database/TableLoad.php +++ b/src/Database/TableLoad.php @@ -22,7 +22,7 @@ * @package Xmf * @author Richard Griffith * @copyright 2013-2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class TableLoad @@ -130,7 +130,7 @@ public static function countRows($table, $criteria = null) $prefixedTable = $db->prefix($table); $sql = 'SELECT COUNT(*) as `count` FROM ' . $prefixedTable . ' '; if (isset($criteria) && is_subclass_of($criteria, '\CriteriaElement')) { - /* @var \CriteriaCompo $criteria */ + /** @var \CriteriaCompo $criteria */ $sql .= $criteria->renderWhere(); } $result = $db->query($sql); @@ -157,7 +157,7 @@ public static function extractRows($table, $criteria = null, $skipColumns = arra $prefixedTable = $db->prefix($table); $sql = 'SELECT * FROM ' . $prefixedTable . ' '; if (isset($criteria) && is_subclass_of($criteria, '\CriteriaElement')) { - /* @var \CriteriaCompo $criteria */ + /** @var \CriteriaCompo $criteria */ $sql .= $criteria->renderWhere(); } $rows = array(); diff --git a/src/Database/Tables.php b/src/Database/Tables.php index 2aabaea..1f71a41 100644 --- a/src/Database/Tables.php +++ b/src/Database/Tables.php @@ -624,7 +624,7 @@ public function delete($table, $criteria) if (is_scalar($criteria)) { $where = $criteria; } elseif (is_object($criteria)) { - /* @var \CriteriaCompo $criteria */ + /** @var \CriteriaCompo $criteria */ $where = $criteria->renderWhere(); } $this->queue[] = "DELETE FROM `{$tableDef['name']}` {$where}"; @@ -685,7 +685,7 @@ public function update($table, $columns, $criteria, $quoteValue = true) if (is_scalar($criteria)) { $where = $criteria; } elseif (is_object($criteria)) { - /* @var \CriteriaCompo $criteria */ + /** @var \CriteriaCompo $criteria */ $where = $criteria->renderWhere(); } $colSql = ''; diff --git a/src/Database/index.php b/src/Database/index.php index ea14d56..db4baed 100644 --- a/src/Database/index.php +++ b/src/Database/index.php @@ -1,2 +1,2 @@ * @author Richard Griffith * @copyright 2011-2021 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class Debug extends \Kint diff --git a/src/Highlighter.php b/src/Highlighter.php index 7b5fd35..8390751 100644 --- a/src/Highlighter.php +++ b/src/Highlighter.php @@ -18,7 +18,7 @@ * @package Xmf * @author Richard Griffith * @copyright 2011-2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class Highlighter diff --git a/src/IPAddress.php b/src/IPAddress.php index 5939f42..b5baf37 100644 --- a/src/IPAddress.php +++ b/src/IPAddress.php @@ -18,7 +18,7 @@ * @package Xmf * @author trabis * @copyright 2018-2021 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class IPAddress diff --git a/src/Jwt/JsonWebToken.php b/src/Jwt/JsonWebToken.php index f062a7b..f52ee7f 100644 --- a/src/Jwt/JsonWebToken.php +++ b/src/Jwt/JsonWebToken.php @@ -22,7 +22,7 @@ * @package Xmf * @author Richard Griffith * @copyright 2018-2023 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class JsonWebToken diff --git a/src/Jwt/KeyFactory.php b/src/Jwt/KeyFactory.php index 1f4c9ef..de6387c 100644 --- a/src/Jwt/KeyFactory.php +++ b/src/Jwt/KeyFactory.php @@ -22,7 +22,7 @@ * @package Xmf * @author Richard Griffith * @copyright 2016-2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class KeyFactory diff --git a/src/Jwt/TokenFactory.php b/src/Jwt/TokenFactory.php index 1936f4a..238a97a 100644 --- a/src/Jwt/TokenFactory.php +++ b/src/Jwt/TokenFactory.php @@ -21,7 +21,7 @@ * @package Xmf * @author Richard Griffith * @copyright 2016-2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class TokenFactory diff --git a/src/Jwt/TokenReader.php b/src/Jwt/TokenReader.php index fc71b49..8bdb199 100644 --- a/src/Jwt/TokenReader.php +++ b/src/Jwt/TokenReader.php @@ -22,7 +22,7 @@ * @package Xmf * @author Richard Griffith * @copyright 2016-2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class TokenReader diff --git a/src/Jwt/index.php b/src/Jwt/index.php index ea14d56..db4baed 100644 --- a/src/Jwt/index.php +++ b/src/Jwt/index.php @@ -1,2 +1,2 @@ * @copyright 2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class ArrayStorage extends \ArrayObject implements StorageInterface diff --git a/src/Key/Basic.php b/src/Key/Basic.php index d7118db..d2296d5 100644 --- a/src/Key/Basic.php +++ b/src/Key/Basic.php @@ -22,7 +22,7 @@ * @package Xmf * @author Richard Griffith * @copyright 2018-2023 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class Basic extends KeyAbstract diff --git a/src/Key/FileStorage.php b/src/Key/FileStorage.php index 32dacbb..26ac6ca 100644 --- a/src/Key/FileStorage.php +++ b/src/Key/FileStorage.php @@ -20,7 +20,7 @@ * @package Xmf * @author Richard Griffith * @copyright 2016-2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class FileStorage implements StorageInterface diff --git a/src/Key/KeyAbstract.php b/src/Key/KeyAbstract.php index e7e12fb..90bff2d 100644 --- a/src/Key/KeyAbstract.php +++ b/src/Key/KeyAbstract.php @@ -20,7 +20,7 @@ * @package Xmf * @author Richard Griffith * @copyright 2018-2023 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ abstract class KeyAbstract diff --git a/src/Key/StorageInterface.php b/src/Key/StorageInterface.php index d7bf87a..e714170 100644 --- a/src/Key/StorageInterface.php +++ b/src/Key/StorageInterface.php @@ -20,7 +20,7 @@ * @package Xmf * @author Richard Griffith * @copyright 2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ interface StorageInterface diff --git a/src/Key/index.php b/src/Key/index.php index ea14d56..db4baed 100644 --- a/src/Key/index.php +++ b/src/Key/index.php @@ -1,2 +1,2 @@ * @copyright 2011-2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class Language diff --git a/src/Metagen.php b/src/Metagen.php index 4de5c88..e52f374 100644 --- a/src/Metagen.php +++ b/src/Metagen.php @@ -19,7 +19,7 @@ * @author Richard Griffith * @author trabis * @copyright 2011-2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class Metagen @@ -59,7 +59,7 @@ public static function assignTitle($title) */ public static function assignKeywords($keywords) { - if (!empty($keywords) && is_array($keywords)) { + if (!empty($keywords) && \is_array($keywords)) { $keyword_tag = implode(', ', $keywords); static::assignThemeMeta('keywords', $keyword_tag); } diff --git a/src/Module/Helper.php b/src/Module/Helper.php index 340c31c..e5e886d 100644 --- a/src/Module/Helper.php +++ b/src/Module/Helper.php @@ -24,7 +24,7 @@ * @author trabis * @author Richard Griffith * @copyright 2011-2023 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class Helper extends GenericHelper diff --git a/src/Module/Helper/AbstractHelper.php b/src/Module/Helper/AbstractHelper.php index 92ae51b..01d7c1b 100644 --- a/src/Module/Helper/AbstractHelper.php +++ b/src/Module/Helper/AbstractHelper.php @@ -20,7 +20,7 @@ * @author trabis * @author Richard Griffith * @copyright 2016-2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ abstract class AbstractHelper @@ -67,7 +67,7 @@ public function __construct($dirname = null) if (isset($xoops)) { $moduleHandler = $xoops->getHandlerModule(); } else { - /* @var \XoopsModuleHandler $moduleHandler */ + /** @var \XoopsModuleHandler $moduleHandler */ $moduleHandler = xoops_getHandler('module'); } $this->module = $moduleHandler->getByDirname($dirname); diff --git a/src/Module/Helper/Cache.php b/src/Module/Helper/Cache.php index a72ac04..f23f564 100644 --- a/src/Module/Helper/Cache.php +++ b/src/Module/Helper/Cache.php @@ -22,7 +22,7 @@ * @author trabis * @author Richard Griffith * @copyright 2011-2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class Cache extends AbstractHelper diff --git a/src/Module/Helper/GenericHelper.php b/src/Module/Helper/GenericHelper.php index c24dd5a..6cbba60 100644 --- a/src/Module/Helper/GenericHelper.php +++ b/src/Module/Helper/GenericHelper.php @@ -25,7 +25,7 @@ * @author trabis * @author Richard Griffith * @copyright 2016-2023 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ abstract class GenericHelper extends AbstractHelper @@ -141,7 +141,7 @@ protected function initObject() ) { $this->module = $xoopsModule; } else { - /* @var \XoopsModuleHandler $module_handler */ + /** @var \XoopsModuleHandler $module_handler */ $module_handler = xoops_getHandler('module'); $this->module = $module_handler->getByDirname($this->dirname); } @@ -163,7 +163,7 @@ protected function initConfig() global $xoopsModuleConfig; $this->configs = $xoopsModuleConfig; } else { - /* @var \XoopsConfigHandler $config_handler */ + /** @var \XoopsConfigHandler $config_handler */ $config_handler = xoops_getHandler('config'); $this->configs = $config_handler->getConfigsByCat(0, $this->getModule()->getVar('mid')); } diff --git a/src/Module/Helper/Permission.php b/src/Module/Helper/Permission.php index 822b39c..32bd3f9 100644 --- a/src/Module/Helper/Permission.php +++ b/src/Module/Helper/Permission.php @@ -21,7 +21,7 @@ * @author trabis * @author Richard Griffith * @copyright 2011-2023 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class Permission extends AbstractHelper @@ -50,7 +50,7 @@ public function init() { $this->mid = $this->module->getVar('mid'); $this->dirname = $this->module->getVar('dirname'); - /* @var $this->permissionHandler XoopsGroupPermHandler */ + /** @var $this->permissionHandler XoopsGroupPermHandler */ $this->permissionHandler = xoops_getHandler('groupperm'); } diff --git a/src/Module/Helper/Session.php b/src/Module/Helper/Session.php index c3d992a..879cdd2 100644 --- a/src/Module/Helper/Session.php +++ b/src/Module/Helper/Session.php @@ -22,7 +22,7 @@ * @author trabis * @author Richard Griffith * @copyright 2011-2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org */ class Session extends AbstractHelper diff --git a/src/Module/Helper/index.php b/src/Module/Helper/index.php index ea14d56..db4baed 100644 --- a/src/Module/Helper/index.php +++ b/src/Module/Helper/index.php @@ -1,2 +1,2 @@ * @author trabis * @copyright 2011-2018 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org * @see https://en.wikipedia.org/wiki/Stop_words */ diff --git a/src/Uuid.php b/src/Uuid.php index fd8fec6..c90f444 100644 --- a/src/Uuid.php +++ b/src/Uuid.php @@ -18,7 +18,7 @@ * @package Xmf * @author Richard Griffith * @copyright 2017-2021 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (https://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) */ class Uuid { @@ -28,7 +28,7 @@ class Uuid /** * generate - generate a version 4 (random) UUID * - * Based on comment by pavel.volyntsev(at)gmail at http://php.net/manual/en/function.com-create-guid.php + * Based on comment by pavel.volyntsev(at)gmail at https://php.net/manual/en/function.com-create-guid.php * * @return string UUID * diff --git a/src/Yaml.php b/src/Yaml.php index f9c214c..f253d5a 100644 --- a/src/Yaml.php +++ b/src/Yaml.php @@ -30,7 +30,7 @@ * @package Xmf * @author Richard Griffith * @copyright 2013-2020 XOOPS Project (https://xoops.org) - * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html) + * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html) * @link https://xoops.org * @see http://www.yaml.org/ */ diff --git a/src/index.php b/src/index.php index ea14d56..db4baed 100644 --- a/src/index.php +++ b/src/index.php @@ -1,2 +1,2 @@ Date: Mon, 30 Oct 2023 11:55:03 -0400 Subject: [PATCH 10/19] PSR-12 --- src/Request.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Request.php b/src/Request.php index 08a9901..d7888c2 100644 --- a/src/Request.php +++ b/src/Request.php @@ -122,13 +122,12 @@ public static function getVar($name, $default = null, $hash = 'default', $type = ) { $var = static::stripSlashesRecursive($var); } + } elseif ($default !== null) { + // Clean the default value + $var = static::cleanVar($default, $mask, $type); } else { - if ($default !== null) { - // Clean the default value - $var = static::cleanVar($default, $mask, $type); - } else { - $var = $default; - } + $var = $default; + } return $var; From 411672609ab0734a81ed42dae2d849eb20f4345a Mon Sep 17 00:00:00 2001 From: mambax7 Date: Mon, 30 Oct 2023 11:57:28 -0400 Subject: [PATCH 11/19] Yoda consistency --- src/Request.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Request.php b/src/Request.php index d7888c2..471ce7e 100644 --- a/src/Request.php +++ b/src/Request.php @@ -81,7 +81,7 @@ public static function getVar($name, $default = null, $hash = 'default', $type = { // Ensure hash and type are uppercase $hash = strtoupper($hash); - if ($hash === 'METHOD') { + if ('METHOD' === $hash) { $hash = static::getMethod(); } $type = strtoupper($type); @@ -111,18 +111,18 @@ public static function getVar($name, $default = null, $hash = 'default', $type = break; } - if (isset($input[$name]) && $input[$name] !== null) { + if (isset($input[$name]) && null !== $input[$name]) { // Get the variable from the input hash and clean it $var = static::cleanVar($input[$name], $mask, $type); // Handle magic quotes compatibility if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() && ($var != $default) - && ($hash !== 'FILES') + && ('FILES' !== $hash) ) { $var = static::stripSlashesRecursive($var); } - } elseif ($default !== null) { + } elseif (null !== $default) { // Clean the default value $var = static::cleanVar($default, $mask, $type); } else { @@ -350,7 +350,7 @@ public static function getHeader($headerName, $default = '') } else { // From joyview - http://php.net/manual/en/function.getallheaders.php foreach ($_SERVER as $name => $value) { - if (substr($name, 0, 5) === 'HTTP_') { + if ('HTTP_' === substr($name, 0, 5)) { $translatedName = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($name, 5)))); $headers[$translatedName] = $value; } @@ -376,7 +376,7 @@ public static function getHeader($headerName, $default = '') public static function hasVar($name, $hash = 'default') { $hash = strtoupper($hash); - if ($hash === 'METHOD') { + if ('METHOD' === $hash) { $hash = strtoupper($_SERVER['REQUEST_METHOD']); } @@ -401,7 +401,7 @@ public static function hasVar($name, $hash = 'default') public static function setVar($name, $value = null, $hash = 'method', $overwrite = true) { $hash = strtoupper($hash); - if ($hash === 'METHOD') { + if ('METHOD' === $hash) { $hash = strtoupper($_SERVER['REQUEST_METHOD']); } @@ -475,7 +475,7 @@ public static function get($hash = 'default', $mask = 0) { $hash = strtoupper($hash); - if ($hash === 'METHOD') { + if ('METHOD' === $hash) { $hash = strtoupper($_SERVER['REQUEST_METHOD']); } @@ -504,7 +504,7 @@ public static function get($hash = 'default', $mask = 0) } // Handle magic quotes compatibility - if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() && ($hash !== 'FILES')) { + if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() && ('FILES' !== $hash)) { $input = static::stripSlashesRecursive($input); } @@ -552,7 +552,7 @@ protected static function cleanVar($var, $mask = 0, $type = null) static $safeHtmlFilter = null; // convert $var in array if $type is ARRAY - if (strtolower((string)$type) === 'array' && !is_array($var)) { + if ('array' === strtolower((string)$type) && !is_array($var)) { $var = array($var); } From 5f62cf8029c16db0c1cb82112014bf4610029d58 Mon Sep 17 00:00:00 2001 From: mambax7 Date: Mon, 30 Oct 2023 16:03:04 -0400 Subject: [PATCH 12/19] PHPUnit 7.4 compatibility --- tests/unit/UlidTest.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/unit/UlidTest.php b/tests/unit/UlidTest.php index 7c20ec5..0662b0c 100644 --- a/tests/unit/UlidTest.php +++ b/tests/unit/UlidTest.php @@ -9,7 +9,7 @@ class UlidTest extends \PHPUnit\Framework\TestCase * Sets up the fixture, for example, opens a network connection. * This method is called before a test is executed. */ - protected function setUp(): void + protected function setUp() { } @@ -17,7 +17,7 @@ protected function setUp(): void * Tears down the fixture, for example, closes a network connection. * This method is called after a test is executed. */ - protected function tearDown(): void + protected function tearDown() { } @@ -33,19 +33,23 @@ public function testGenerate() $this->assertNotEquals($ulid1, $ulid2, 'ULIDs should be unique'); $this->assertTrue(strcasecmp($ulid1, $ulid2) < 0, 'ULIDs should collate correctly'); } - public function testGeneratesLowercaseIdentifierWhenConfigured(): void + public function testGeneratesLowercaseIdentifierWhenConfigured() { $ulid = Ulid::generate(false); //generate lower case - $this->assertMatchesRegularExpression('/[0-9][a-z]/', $ulid); + if (method_exists($this, 'assertMatchesRegularExpression')) { + $this->assertMatchesRegularExpression('/[0-9][a-z]/', $ulid); + } else { + $this->assertRegExp('/[0-9][a-z]/', $ulid); + } } - public function testGeneratesTwentySixChars(): void + public function testGeneratesTwentySixChars() { $this->assertSame(26, strlen(Ulid::generate())); } - public function testRandomnessWhenGeneratedMultipleTimes(): void + public function testRandomnessWhenGeneratedMultipleTimes() { $a = Ulid::generate(); $b = Ulid::generate(); @@ -63,7 +67,7 @@ public function testRandomnessWhenGeneratedMultipleTimes(): void $this->assertNotEquals(substr($a, 10), substr($b, 10)); } - public function testGeneratesLexographicallySortableUlids(): void + public function testGeneratesLexographicallySortableUlids() { $a = Ulid::generate(); From a8fa00c40966a685ed17f30fea4b3cf4429c330f Mon Sep 17 00:00:00 2001 From: Michael Beck Date: Mon, 30 Oct 2023 17:52:15 -0400 Subject: [PATCH 13/19] Update pr_tests.yml adding PHP 8.1 --- .github/workflows/pr_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml index cb58324..62b7829 100644 --- a/.github/workflows/pr_tests.yml +++ b/.github/workflows/pr_tests.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - php_version: ["7.1", "7.4"] + php_version: ["7.1", "7.4", "8.1"] runs-on: ubuntu-latest steps: From fcb56e9c93c6de7916c8a277456bdc866751ff44 Mon Sep 17 00:00:00 2001 From: mambax7 Date: Mon, 30 Oct 2023 17:53:03 -0400 Subject: [PATCH 14/19] updates --- src/Ulid.php | 1 - tests/unit/UlidTest.php | 28 +++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Ulid.php b/src/Ulid.php index 0590d8d..bdaaf0f 100644 --- a/src/Ulid.php +++ b/src/Ulid.php @@ -31,7 +31,6 @@ class Ulid * @return string The generated ULID. * @throws \Exception */ - public static function generate(bool $upperCase = true): string { $time = (int)(microtime(true) * 1000000); diff --git a/tests/unit/UlidTest.php b/tests/unit/UlidTest.php index 0662b0c..373144d 100644 --- a/tests/unit/UlidTest.php +++ b/tests/unit/UlidTest.php @@ -22,6 +22,9 @@ protected function tearDown() } /** + * Tests that the `generate()` method generates a unique ULID. + * + * @covers Xmf\Ulid::generate * @throws \Exception */ public function testGenerate() @@ -33,6 +36,12 @@ public function testGenerate() $this->assertNotEquals($ulid1, $ulid2, 'ULIDs should be unique'); $this->assertTrue(strcasecmp($ulid1, $ulid2) < 0, 'ULIDs should collate correctly'); } + + /** + * Tests that the `generate()` method generates a lowercase ULID when configured to do so. + * + * @covers Xmf\Ulid::generate + */ public function testGeneratesLowercaseIdentifierWhenConfigured() { $ulid = Ulid::generate(false); //generate lower case @@ -44,11 +53,23 @@ public function testGeneratesLowercaseIdentifierWhenConfigured() } } + /** + * Tests that the `generate()` method generates a 26-character ULID. + * + * @covers Xmf\Ulid::generate + */ public function testGeneratesTwentySixChars() { - $this->assertSame(26, strlen(Ulid::generate())); + $ulid = Ulid::generate(); + + $this->assertSame(26, strlen($ulid)); } + /** + * Tests that the `generate()` method generates ULIDs with different random characters when generated multiple times. + * + * @covers Xmf\Ulid::generate + */ public function testRandomnessWhenGeneratedMultipleTimes() { $a = Ulid::generate(); @@ -67,6 +88,11 @@ public function testRandomnessWhenGeneratedMultipleTimes() $this->assertNotEquals(substr($a, 10), substr($b, 10)); } + /** + * Tests that the `generate()` method generates lexicographically sortable ULIDs. + * + * @covers Xmf\Ulid::generate + */ public function testGeneratesLexographicallySortableUlids() { $a = Ulid::generate(); From 0a70e5894855c315326d8daa7632870700d41169 Mon Sep 17 00:00:00 2001 From: Michael Beck Date: Mon, 30 Oct 2023 18:10:33 -0400 Subject: [PATCH 15/19] Update pr_tests.yml --- .github/workflows/pr_tests.yml | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml index 62b7829..87557f4 100644 --- a/.github/workflows/pr_tests.yml +++ b/.github/workflows/pr_tests.yml @@ -3,7 +3,7 @@ name: CI on: [push, pull_request] jobs: - phpunit: + phpunit-7-1: strategy: fail-fast: false matrix: @@ -20,5 +20,25 @@ jobs: with: php-version: ${{ matrix.php_version }} coverage: xdebug - - name: Unit Tests - run: vendor/bin/phpunit --stderr + - name: Unit Tests with PHPUnit 7.1 + run: vendor/bin/phpunit --stderr --version 7.1 + + phpunit-9-6: + strategy: + fail-fast: false + matrix: + php_version: ["7.1", "7.4", "8.1"] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: php-actions/composer@v6 + with: + php_version: "7.4" + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php_version }} + coverage: xdebug + - name: Unit Tests with PHPUnit 9.6 + run: vendor/bin/phpunit --stderr --version 9.6 From db5dd3df88faf05426cf389db8da468e15214226 Mon Sep 17 00:00:00 2001 From: mambax7 Date: Mon, 30 Oct 2023 18:27:47 -0400 Subject: [PATCH 16/19] add new asserts --- tests/unit/UlidTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/UlidTest.php b/tests/unit/UlidTest.php index 373144d..fa7820c 100644 --- a/tests/unit/UlidTest.php +++ b/tests/unit/UlidTest.php @@ -30,6 +30,7 @@ protected function tearDown() public function testGenerate() { $ulid1 = Ulid::generate(); + $this->assertTrue(Ulid::isValid($ulid1)); usleep(2000); // Wait for 2 milliseconds to ensure a different timestamp $ulid2 = Ulid::generate(); @@ -73,7 +74,9 @@ public function testGeneratesTwentySixChars() public function testRandomnessWhenGeneratedMultipleTimes() { $a = Ulid::generate(); + usleep(100); // Wait for 100 microseconds to ensure a different timestamp $b = Ulid::generate(); + $this->assertLessThan($a, $b); // The time parts are different. $this->assertNotEquals(substr($a, 0, 10), substr($b, 0, 10)); From ec44dbf8ebb75584c52fad9350510755007f3c9f Mon Sep 17 00:00:00 2001 From: mambax7 Date: Tue, 31 Oct 2023 11:55:45 -0400 Subject: [PATCH 17/19] updates, more tests --- src/Ulid.php | 150 ++++++++++-- tests/unit/UlidTest.php | 529 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 644 insertions(+), 35 deletions(-) diff --git a/src/Ulid.php b/src/Ulid.php index bdaaf0f..887119f 100644 --- a/src/Ulid.php +++ b/src/Ulid.php @@ -29,45 +29,165 @@ class Ulid * Generate a new ULID. * * @return string The generated ULID. - * @throws \Exception */ public static function generate(bool $upperCase = true): string { - $time = (int)(microtime(true) * 1000000); + $time = self::microtimeToUlidTime(\microtime(true)); $timeChars = self::encodeTime($time); $randChars = self::encodeRandomness(); $ulid = $timeChars . $randChars; - return $upperCase ? strtoupper($ulid) : strtolower($ulid); + $ulid = $upperCase ? \strtoupper($ulid) : \strtolower($ulid); + + return $ulid; } - private static function encodeTime(int $time): string + /** + * @param int $time + * + * @return string + */ + public static function encodeTime(int $time): string { + $encodingCharsArray = str_split(self::ENCODING_CHARS); $timeChars = ''; for ($i = 0; $i < 10; $i++) { - $mod = $time % self::ENCODING_LENGTH; - $timeChars = self::ENCODING_CHARS[$mod] . $timeChars; - $time = ($time - $mod) / self::ENCODING_LENGTH; + $mod = \floor($time % self::ENCODING_LENGTH); + $timeChars = $encodingCharsArray[$mod] . $timeChars; + $time = (int)(($time - $mod) / self::ENCODING_LENGTH); } return $timeChars; } - /** - * @throws \Exception - */ - private static function encodeRandomness(): string + public static function encodeRandomness(): string { - $randomBytes = random_bytes(10); // 80 bits + $encodingCharsArray = str_split(self::ENCODING_CHARS); + $randomBytes = \random_bytes(10); // 80 bits + // Check if the random bytes were generated successfully. + if (false === $randomBytes) { + throw new \RuntimeException('Failed to generate random bytes'); + } + $randChars = ''; for ($i = 0; $i < 16; $i++) { - $randValue = ord($randomBytes[$i % 10]); - if ($i % 2 === 0) { + $randValue = \ord($randomBytes[$i % 10]); + if (0 === $i % 2) { $randValue >>= 3; // take the upper 5 bits } else { $randValue &= 31; // take the lower 5 bits } - $randChars .= self::ENCODING_CHARS[$randValue]; + $randChars .= $encodingCharsArray[$randValue]; } return $randChars; } + + /** + * @param string $ulid + * + * @return array + */ + public static function decode(string $ulid): array + { + if (!self::isValid($ulid)) { + throw new \InvalidArgumentException('Invalid ULID string'); + } + + $time = self::decodeTime($ulid); + $rand = self::decodeRandomness($ulid); + + return [ + 'time' => $time, + 'rand' => $rand, + ]; + } + + /** + * @param string $ulid + * + * @return int + */ + public static function decodeTime(string $ulid): int + { +// $encodingCharsArray = str_split(self::ENCODING_CHARS); + + // Check if the ULID string is valid. + if (!self::isValid($ulid)) { + throw new \InvalidArgumentException('Invalid ULID string'); + } + + $time = 0; + for ($i = 0; $i < 10; $i++) { + $char = $ulid[$i]; + $value = \strpos(self::ENCODING_CHARS, $char); + $exponent = 9 - $i; + $time += $value * \bcpow((string)self::ENCODING_LENGTH, (string)$exponent); + } + + return $time; + } + + /** + * @param string $ulid + * + * @return int + */ + public static function decodeRandomness(string $ulid): int + { + if (26 !== strlen($ulid)) { + throw new \InvalidArgumentException('Invalid ULID length'); // Changed line + } + + $rand = 0; + for ($i = 10; $i < 26; $i++) { + $char = $ulid[$i]; + $value = \strpos(self::ENCODING_CHARS, $char); + + // Check if the random value is within the valid range. + if ($value < 0 || $value >= self::ENCODING_LENGTH) { + throw new \InvalidArgumentException('Invalid ULID random value'); + } + $exponent = 15 - $i; + $rand += $value * \bcpow((string)self::ENCODING_LENGTH, (string)$exponent); + } + + return $rand; + } + + /** + * @param string $ulid + * + * @return bool + */ + public static function isValid(string $ulid): bool + { + // Check the length of the ULID string before throwing an exception. + if (26 !== strlen($ulid)) { + return false; + } + + // Throw an exception if the ULID is invalid. + try { + self::decodeRandomness($ulid); + } catch (\InvalidArgumentException $e) { + return false; } + + return true; + } + + /** + * @param float $microtime + * + * @return int + */ + public static function microtimeToUlidTime(float $microtime): int + { + $timestamp = $microtime * 1000000; + $unixEpoch = 946684800000000; // Microseconds since the Unix epoch. + + return (int)($timestamp - $unixEpoch); + } +} + + + diff --git a/tests/unit/UlidTest.php b/tests/unit/UlidTest.php index fa7820c..ed4f5dc 100644 --- a/tests/unit/UlidTest.php +++ b/tests/unit/UlidTest.php @@ -1,23 +1,28 @@ -assertTrue(Ulid::isValid($ulid)); + + // Assert that the ULID string is always in uppercase. + $this->assertEquals($ulid, \strtoupper($ulid)); + + // Assert that the ULID string is unique. + $this->assertNotEquals($ulid, Ulid::generate()); + $ulid1 = Ulid::generate(); $this->assertTrue(Ulid::isValid($ulid1)); - usleep(2000); // Wait for 2 milliseconds to ensure a different timestamp + \usleep(2000); // Wait for 2 milliseconds to ensure a different timestamp $ulid2 = Ulid::generate(); $this->assertNotEquals($ulid1, $ulid2, 'ULIDs should be unique'); - $this->assertTrue(strcasecmp($ulid1, $ulid2) < 0, 'ULIDs should collate correctly'); + $this->assertTrue(\strcasecmp($ulid1, $ulid2) < 0, 'ULIDs should collate correctly'); + } + + /** + * @return void + * @throws \Exception + */ + public function testGenerateUlidUpperCase() + { + $ulid = Ulid::generate(true); + $this->assertTrue(Ulid::isValid($ulid)); + $this->assertEquals(\strtoupper($ulid), $ulid); + } + + /** + * @return void + * @throws \Exception + */ + public function testGenerateUlidLowerCase() + { + $ulid = Ulid::generate(false); + $this->assertTrue(Ulid::isValid($ulid)); + $this->assertEquals(\strtolower($ulid), $ulid); + } + + /** + * @return void + * @throws \Exception + */ + public function testDecode() + { + $ulid = Ulid::generate(); + + // Decode the ULID string. + $components = Ulid::decode($ulid); + + // Assert that the decoded time and randomness components are valid. + $this->assertGreaterThan(0, $components['time']); + $this->assertGreaterThan(0, $components['rand']); + + // Assert that the decoded time and randomness components are within the valid range. + $this->assertLessThanOrEqual(PHP_INT_MAX, $components['time']); + $this->assertLessThanOrEqual(PHP_INT_MAX, $components['rand']); } /** * Tests that the `generate()` method generates a lowercase ULID when configured to do so. * * @covers Xmf\Ulid::generate + * @throws \Exception */ public function testGeneratesLowercaseIdentifierWhenConfigured() { $ulid = Ulid::generate(false); //generate lower case - if (method_exists($this, 'assertMatchesRegularExpression')) { + if (\method_exists($this, 'assertMatchesRegularExpression')) { $this->assertMatchesRegularExpression('/[0-9][a-z]/', $ulid); } else { $this->assertRegExp('/[0-9][a-z]/', $ulid); @@ -58,56 +117,486 @@ public function testGeneratesLowercaseIdentifierWhenConfigured() * Tests that the `generate()` method generates a 26-character ULID. * * @covers Xmf\Ulid::generate + * @throws \Exception */ public function testGeneratesTwentySixChars() { $ulid = Ulid::generate(); - $this->assertSame(26, strlen($ulid)); + $this->assertSame(26, \strlen($ulid)); } /** * Tests that the `generate()` method generates ULIDs with different random characters when generated multiple times. * * @covers Xmf\Ulid::generate + * @throws \Exception + * @throws \Exception */ public function testRandomnessWhenGeneratedMultipleTimes() { $a = Ulid::generate(); - usleep(100); // Wait for 100 microseconds to ensure a different timestamp + \usleep(100); // Wait for 100 microseconds to ensure a different timestamp $b = Ulid::generate(); - $this->assertLessThan($a, $b); + $this->assertLessThan($b, $a); + + // Using strcmp for lexicographical comparison + $this->assertTrue(\strcmp($a, $b) < 0); // The time parts are different. - $this->assertNotEquals(substr($a, 0, 10), substr($b, 0, 10)); + $this->assertNotEquals(\substr($a, 0, 10), \substr($b, 0, 10)); //the second ULID time part is bigger than the first ULID - $this->assertGreaterThan(substr($a, 0, 10), substr($b, 0, 10)); + $this->assertGreaterThan(\substr($a, 0, 10), \substr($b, 0, 10)); - // Only the last time character should be different. - $this->assertEquals(substr($a, 0, 9), substr($b, 0, 9)); + // The first 5-6 characters should be the same + $this->assertEquals(\substr($a, 0, 5), \substr($b, 0, 5)); //the random characters part should be different - $this->assertNotEquals(substr($a, 10), substr($b, 10)); + $this->assertNotEquals(\substr($a, 10), \substr($b, 10)); } /** * Tests that the `generate()` method generates lexicographically sortable ULIDs. * * @covers Xmf\Ulid::generate + * @throws \Exception + * @throws \Exception */ public function testGeneratesLexographicallySortableUlids() { $a = Ulid::generate(); - sleep(1); + \usleep(1000); // Wait for 1 millisecond to ensure a different timestamp $b = Ulid::generate(); - $ulids = [(string) $b, (string) $a]; - usort($ulids, 'strcmp'); + $ulids = [$b, $a]; + \usort($ulids, 'strcmp'); - $this->assertSame([(string) $a, (string) $b], $ulids); + $this->assertSame([$a, $b], $ulids); } -} + /** + * @return void + * @throws \Exception + */ + public function testIsValid() + { + $ulid = Ulid::generate(); + + // Assert that the isValid() method returns true for valid ULID strings. + $this->assertTrue(Ulid::isValid($ulid)); + + // Assert that the isValid() method returns false for invalid ULID strings. + $invalidUlid = 'invalid-ulid'; + $this->assertFalse(Ulid::isValid($invalidUlid)); + } + + /** + * @return void + */ + public function testEncodeTime() + { + $time = 572826470852228; + $timeChars = Ulid::encodeTime($time); // Assumes encodeTime is public + + $this->assertEquals('G8ZE7509M4', $timeChars); + } + + /** + * @return void + * @throws \Exception + */ + public function testDecodeTime() + { + $timeChars = 'G8ZE7509M4SNQGYN4H6GSNQGYN'; + $time = Ulid::decodeTime($timeChars); + $this->assertEquals(572826470852228, $time); + } + + /** + * @return void + * @throws \Exception + */ + public function testEncodeRandomness() + { + $randChars = Ulid::encodeRandomness(); + //Checking the length of randomness characters + $this->assertEquals(16, \strlen($randChars)); + } + + /** + * @return void + * @throws \Exception + */ + public function testDecodeRandomness() + { + $randChars = Ulid::generate(); + + $rand = Ulid::decodeRandomness($randChars); + // Assert that the randomness value is within the valid range. + $this->assertGreaterThanOrEqual(0, $rand); + $this->assertLessThanOrEqual(PHP_INT_MAX, $rand); + + // Assert that the randomness value is different from a known value. + $this->assertNotEquals(1234567890, $rand); + } + + + /** + * @return void + * @throws \Exception + */ + public function testGenerateUnique() + { + $ulid1 = Ulid::generate(); + $ulid2 = Ulid::generate(); + + $this->assertNotEquals($ulid1, $ulid2); + } + + /** + * @return void + */ + public function testDecodeException() + { + + $invalidUlid = 'invalid-ulid'; + + $this->expectException(\InvalidArgumentException::class); + Ulid::decode($invalidUlid); + } + + /** + * @return void + */ + public function testIsValidException() + { + $invalidUlid = 'invalid-ulid'; + + $isValid = Ulid::isValid($invalidUlid); + + $this->assertFalse($isValid); + } + + /** + * @return void + * @throws \Exception + */ + public function testDecodeUlid() + { + $ulid = Ulid::generate(); + $components = Ulid::decode($ulid); + + $this->assertTrue($components['rand'] >= 0 && $components['rand'] < (32 ** 16)); + } + + /** + * @return void + */ + public function testDecodeInvalidUlid() + { + $invalidUlid = 'invalid-ulid'; + + // Assert that the decode() method throws an exception of the correct type for invalid ULID strings. + $this->expectException(\InvalidArgumentException::class); + Ulid::decode($invalidUlid); + } + + // Validate ULID + + /** + * @return void + * @throws \Exception + */ + public function testValidateUlid() + { + $ulid = Ulid::generate(); + + $this->assertTrue(Ulid::isValid($ulid)); + } + + /** + * @return void + */ + public function testValidateInvalidUlid() + { + //INVALID + $this->assertFalse(Ulid::isValid('invalid-ulid-string')); + } + + // Test ULID Generation: + + /** + * @return void + * @throws \Exception + */ + public function testUlidGeneration() + { + $ulid = Ulid::generate(); + $this->assertNotEmpty($ulid); + $this->assertSame(26, \strlen($ulid)); + } + + /** + * @return void + * @throws \Exception + */ + public function testUlidUniqueness() + { + $ulid1 = Ulid::generate(); + \usleep(1000); // Wait for 1 millisecond to ensure a different timestamp + $ulid2 = Ulid::generate(); + $this->assertNotEquals($ulid1, $ulid2); + } + + /** + * @return void + * @throws \Exception + */ + public function testCaseSensitivity() + { + $ulidUpperCase = Ulid::generate(true); + $ulidLowerCase = Ulid::generate(false); + + // Assert that the two ULID strings are different. + $this->assertNotEquals($ulidUpperCase, $ulidLowerCase); + + echo "ulidUpperCase: $ulidUpperCase\n"; + echo "ulidLowerCase: $ulidLowerCase\n"; + } + + // Test ULID Decoding: + + /** + * @return void + * @throws \Exception + */ + public function testUlidDecoding() + { + $ulid = Ulid::generate(); + $components = Ulid::decode($ulid); + $this->assertArrayHasKey('time', $components); + $this->assertArrayHasKey('rand', $components); + } + + /** + * @return void + */ + public function testInvalidUlidDecoding() + { + + $this->expectException(\InvalidArgumentException::class); + Ulid::decode('invalidulid'); + } + + // Test ULID Encoding: + // (assuming you have a method to encode time and randomness separately) + /** + * @return void + */ + public function testTimeEncoding() + { + $time = Ulid::microtimeToUlidTime(\microtime(true)); + $encodedTime = Ulid::encodeTime($time); // Assumes encodeTime is public + $this->assertNotEmpty($encodedTime); + } + + // Test ULID Validity Checking: + + /** + * @return void + * @throws \Exception + */ + public function testValidUlid() + { + $ulid = Ulid::generate(); + $this->assertTrue(Ulid::isValid($ulid)); + } + + /** + * @return void + */ + public function testInvalidUlid() + { + $this->assertFalse(Ulid::isValid('invalidulid')); + } + + // Test Lexicographic Order: + + /** + * @return void + * @throws \Exception + */ + public function testLexicographicOrder() + { + $ulid1 = Ulid::generate(); + \usleep(1000); + $ulid2 = Ulid::generate(); + $this->assertTrue(\strcmp($ulid1, $ulid2) < 0); + } + + // Test Microtime Conversion: + + /** + * @return void + */ + public function testMicrotimeConversion() + { + $microtime = \microtime(true); + $ulidTime = Ulid::microtimeToUlidTime($microtime); + + $this->assertIsInt($ulidTime); + // Check if the time is within a reasonable range (e.g., since the year 2000) + $this->assertGreaterThanOrEqual($ulidTime, 946684800000000); + } + + // Test the decoding of time from a given ULID + + /** + * @return void + * @throws \Exception + */ + public function testDecodeTimeInt() + { + $ulid = Ulid::generate(); + $decodedTime = Ulid::decodeTime($ulid); // Assumes decodeTime is public + $this->assertIsInt($decodedTime); + } + + // Test the decoding of randomness from a given ULID + + /** + * @return void + * @throws \Exception + */ + public function testDecodeRandomnessInt() + { + $ulid = Ulid::generate(); + $decodedRandomness = Ulid::decodeRandomness($ulid); // Assumes decodeRandomness is public + + $this->assertIsInt($decodedRandomness); + } + + // Test the encoding of randomness + + /** + * @return void + * @throws \Exception + */ + public function testEncodeRandomnessNotEmpty() + { + $encodedRandomness = Ulid::encodeRandomness(); // Assumes encodeRandomness is public + $this->assertNotEmpty($encodedRandomness); + } + + // Test for valid random value range + + /** + * @return void + * @throws \Exception + */ + public function testRandomValueRange() + { + $ulid = Ulid::generate(); + $components = Ulid::decode($ulid); + $this->assertGreaterThanOrEqual(0, $components['rand']); + // Assuming a maximum value for the random component: + $this->assertLessThanOrEqual(PHP_INT_MAX, $components['rand']); + } + + // Test encoding and decoding consistency + + /** + * @return void + * @throws \Exception + */ + public function testEncodingDecodingConsistency() + { + $ulid = Ulid::generate(); + $components = Ulid::decode($ulid); + $encodedTime = Ulid::encodeTime($components['time']); // Assumes encodeTime is public + $this->assertEquals(\substr($ulid, 0, 10), $encodedTime); + } + + // Test if the microtimeToUlidTime function is working as expected + + /** + * @return void + */ + public function testMicrotimeToUlidTimeFunction() + { + $microtime = \microtime(true); + $ulidTime = Ulid::microtimeToUlidTime($microtime); + + $this->assertIsInt($ulidTime); + + // Check if the time is within a reasonable range (e.g., since the year 2000) + $this->assertGreaterThanOrEqual($ulidTime, 946684800000000); + } + + /** + * Test for valid ULID string format + * @throws \Exception + */ + public function testValidUlidString() + { + $ulid = Ulid::generate(); + + $this->assertTrue(Ulid::isValid($ulid)); + } + + /** + * Test for invalid ULID string format + */ + public function testInvalidUlidString() + { + $invalidUlid = 'INVALID_ULID_STRING'; + + $this->assertFalse(Ulid::isValid($invalidUlid)); + } + + /** + * Test for case insensitivity in ULID validation + * @throws \Exception + */ + public function testCaseInsensitivity() + { + $ulid = Ulid::generate(false); // generate lowercase ULID + $this->assertTrue(Ulid::isValid(\strtoupper($ulid))); + } + + /** + * Test exception handling for invalid ULID string in decode method + */ + public function testDecodeExceptionHandling() + { + + $this->expectException(\InvalidArgumentException::class); + $invalidUlid = 'INVALID_ULID_STRING'; + Ulid::decode($invalidUlid); + } + + /** + * Test exception handling for invalid ULID string in decodeTime method + */ + public function testDecodeTimeExceptionHandling() + { + $invalidUlid = 'INVALID_ULID_STRING'; + + $this->expectException(\InvalidArgumentException::class); + Ulid::decodeTime($invalidUlid); // Assumes decodeTime is public + } + + /** + * Test exception handling for invalid ULID string in decodeRandomness method + */ + public function testDecodeRandomnessExceptionHandling() + { + $invalidUlid = 'INVALID_ULID_STRING'; + + $this->expectException(\InvalidArgumentException::class); + Ulid::decodeRandomness($invalidUlid); // Assumes decodeRandomness is public + } +} From 9984a64e23120d41955dda164c1538036fcadeac Mon Sep 17 00:00:00 2001 From: Michael Beck Date: Tue, 31 Oct 2023 11:58:54 -0400 Subject: [PATCH 18/19] Update pr_tests.yml --- .github/workflows/pr_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml index 87557f4..89131dc 100644 --- a/.github/workflows/pr_tests.yml +++ b/.github/workflows/pr_tests.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - php_version: ["7.1", "7.4", "8.1"] + php_version: ["7.1", "7.4"] runs-on: ubuntu-latest steps: @@ -27,7 +27,7 @@ jobs: strategy: fail-fast: false matrix: - php_version: ["7.1", "7.4", "8.1"] + php_version: ["8.1"] runs-on: ubuntu-latest steps: From 52ed3ec1e52dda5e2365c0b206f6cb8ac822d279 Mon Sep 17 00:00:00 2001 From: Michael Beck Date: Tue, 31 Oct 2023 11:59:47 -0400 Subject: [PATCH 19/19] Update pr_tests.yml