diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f18408d..c293e0d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,6 +39,9 @@ jobs: - name: Install dependencies run: composer install --prefer-dist + + - name: Run PHPStan + run: vendor/bin/phpstan analyse -l 10 source/ tests/ - name: Run unit tests run: vendor/bin/phpunit --no-coverage tests/ \ No newline at end of file diff --git a/composer.json b/composer.json index 9beee79..815763a 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ } }, "require-dev": { + "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^11.5" } } diff --git a/composer.lock b/composer.lock index 192b1df..f4d6fcc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "16402bdc3869a1fa91d1b374e01cc383", + "content-hash": "6a38a22a0339f24f176eebd93864f02b", "packages": [], "packages-dev": [ { @@ -243,6 +243,64 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpstan/phpstan", + "version": "2.1.6", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", + "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-02-19T15:46:42+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "11.0.9", diff --git a/source/AlnumVigenereCipher.php b/source/AlnumVigenereCipher.php index 301377a..01475d4 100644 --- a/source/AlnumVigenereCipher.php +++ b/source/AlnumVigenereCipher.php @@ -8,39 +8,77 @@ * This file is the main class for alpha-numric mode vigenere cipher algortithm * * @author Fahmi Auliya Tsani - * @version 0.1 + * @version 1.1 */ - class AlnumVigenereCipher extends VigenereCipherBlueprint { + public function __construct( + public string $data, + public string $key, + public string $process = 'encrypt' + ) { + $this->process = $process; + $this->tabulaRecta = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + if ($process == ProcessType::ENCRYPT->value) { + $this->plainText = $data; + $this->key = $this->generateKey($key); + + if ($this->isValid()) { + $this->encrypt(); + } + } else { + $this->cipherText = $data; + $this->key = $this->generateKey($key); + + if ($this->isValid()) { + $this->decrypt(); + } + } + } + /** - * Default list of acceptable character to be used in vigenere cipher algorithm - * This list cointains alpha-numeric characters including the capitalized - * - * @var string + * @inheritdoc */ - public $tabulaRecta = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + public function isValidKey(string $pattern): bool + { + return preg_match($pattern, $this->key) == 1; + } + /** + * @inheritdoc + */ + public function isValidPlainText(string $pattern): bool + { + return preg_match($pattern, $this->plainText) == 1; + } + + /** + * @inheritdoc + */ + public function isValidCipherText(string $pattern): bool + { + return preg_match($pattern, $this->cipherText) == 1; + } + + /** + * @inheritdoc + */ public function isValid(): bool { try { $pattern = '/^[a-zA-Z0-9]*$/'; - $isValid = preg_match($pattern, $this->key); - if (! $isValid) { + if (! $this->isValidKey($pattern)) { throw new InvalidAlnumException('Key'); } if ($this->process == ProcessType::ENCRYPT->value) { - $isValid = preg_match($pattern, $this->plainText) && $isValid; - - if (! $isValid) { + if (! $this->isValidPlainText($pattern)) { throw new InvalidAlnumException('Plain text'); } } else { - $isValid = preg_match($pattern, $this->cipherText) && $isValid; - - if (! $isValid) { + if (! $this->isValidCipherText($pattern)) { throw new InvalidAlnumException('Cipher text'); } } @@ -50,6 +88,7 @@ public function isValid(): bool return false; } + $this->setIsValid(true); return true; } } diff --git a/source/BasicVigenereCipher.php b/source/BasicVigenereCipher.php index f4b615e..962ade5 100644 --- a/source/BasicVigenereCipher.php +++ b/source/BasicVigenereCipher.php @@ -8,36 +8,78 @@ * This file is the main class for basic vigenere cipher algortithm * * @author Fahmi Auliya Tsani - * @version 0.2 + * @version 1.1 */ - +#[\AllowDynamicProperties] class BasicVigenereCipher extends VigenereCipherBlueprint { + public function __construct( + public string $data, + public string $key, + public string $process = 'encrypt' + ) { + $this->process = $process; + $this->tabulaRecta = 'abcdefghijklmnopqrstuvwxyz'; + + if ($process == ProcessType::ENCRYPT->value) { + $this->plainText = $data; + $this->key = $this->generateKey($key); + + if ($this->isValid()) { + $this->encrypt(); + } + } else { + $this->cipherText = $data; + $this->key = $this->generateKey($key); + + if ($this->isValid()) { + $this->decrypt(); + } + } + } + /** * @inheritdoc */ - public $tabulaRecta = 'abcdefghijklmnopqrstuvwxyz'; + public function isValidKey(string $pattern): bool + { + return preg_match($pattern, $this->key) == 1; + } + /** + * @inheritdoc + */ + public function isValidPlainText(string $pattern): bool + { + return preg_match($pattern, $this->plainText) == 1; + } + + /** + * @inheritdoc + */ + public function isValidCipherText(string $pattern): bool + { + return preg_match($pattern, $this->cipherText) == 1; + } + + /** + * @inheritdoc + */ public function isValid(): bool { try { $pattern = '/^[a-z]*$/'; - $isValid = preg_match($pattern, $this->key); - if (! $isValid) { + if (! $this->isValidKey($pattern)) { throw new InvalidBasicException('Key'); } if ($this->process == ProcessType::ENCRYPT->value) { - $isValid = preg_match($pattern, $this->plainText) && $isValid; - - if (! $isValid) { + if (! $this->isValidPlainText($pattern)) { throw new InvalidBasicException('Plain text'); } } else { - $isValid = preg_match($pattern, $this->cipherText) && $isValid; - - if (! $isValid) { + if (! $this->isValidCipherText($pattern)) { throw new InvalidBasicException('Cipher text'); } } @@ -47,6 +89,8 @@ public function isValid(): bool return false; } + $this->setIsValid(true); + return true; } } diff --git a/source/VigenereCipher.php b/source/VigenereCipher.php index e0aa1a2..179a805 100644 --- a/source/VigenereCipher.php +++ b/source/VigenereCipher.php @@ -1,6 +1,7 @@ value) { - return $path . 'BasicVigenereCipher'; - } elseif ($mode == VigenereMode::ALPHA_NUMERIC->value) { - return $path . 'AlnumVigenereCipher'; + if ($mode == VigenereMode::ALPHA_NUMERIC->value) { + $className = 'AlnumVigenereCipher'; } + + return $path . $className; } - public static function encrypt(string $data, string $key, string $mode = 'basic'): string|null + public static function encrypt(string $data, string $key, string $mode = 'basic'): string { $className = self::getClassName($mode); $processName = ProcessType::ENCRYPT->value; - $encrypt = new $className($processName, $data, $key); + /** @var VigenereCipherBlueprint $encrypt */ + $encrypt = new $className($data, $key, $processName); - return $encrypt->getCipherText(); + return $encrypt->cipherText; } - public static function decrypt(string $data, string $key, string $mode = 'basic'): string|null + public static function decrypt(string $data, string $key, string $mode = 'basic'): string { $className = self::getClassName($mode); $processName = ProcessType::DECRYPT->value; - $decrypt = new $className($processName, $data, $key); + /** @var VigenereCipherBlueprint $decrypt */ + $decrypt = new $className($data, $key, $processName); - return $decrypt->getPlainText(); + return $decrypt->plainText; } } diff --git a/source/VigenereCipherBlueprint.php b/source/VigenereCipherBlueprint.php index bd434d6..9881a3b 100644 --- a/source/VigenereCipherBlueprint.php +++ b/source/VigenereCipherBlueprint.php @@ -3,6 +3,7 @@ use amculin\cryptography\classic\enums\ProcessType; +#[\AllowDynamicProperties] abstract class VigenereCipherBlueprint { /** @@ -11,62 +12,95 @@ abstract class VigenereCipherBlueprint * * @var string */ - public $tabulaRecta; + public string $tabulaRecta; /** * The current process whether it is encrypt or decrypt * * @var string */ - public $process; + public string $process; /** * The plain text/message to be encrypted * * @var string */ - public $plainText; + public string $plainText = ''; /** * The key used to encrypt plain text/message * * @var string */ - public $key; + public string $key; + + /** + * The isValid attribute to ensure whether the key, plainText, or cipherText + * is valid + * + * @var bool + */ + public bool $isValid = false; /** * The cipher text to be decrypted * * @var string */ - public $cipherText; + public string $cipherText = ''; + + /** + * Method to validate key, plainText, and cipherText + * + * @return bool + */ + abstract public function isValid(): bool; + + /** + * Method to validate the key using regex pattern + * + * @param string $pattern + * @return bool + */ + abstract public function isValidKey(string $pattern): bool; + + /** + * Method to validate the plain text using regex pattern + * + * @param string $pattern + * @return bool + */ + abstract public function isValidPlainText(string $pattern): bool; + + /** + * Method to validate the cipher text using regex pattern + * + * @param string $pattern + * @return bool + */ + abstract public function isValidCipherText(string $pattern): bool; - public function __construct(string $process = 'encrypt', string $data = null, string $key = null) + /** + * Method to get is valid status + * + * @return bool + */ + public function getIsValid(): bool { - $this->setProcess($process); - - if ($process == ProcessType::ENCRYPT->value) { - if (! is_null($data) && ! is_null($key)) { - $this->setPlainText($data); - $this->setKey($key); - - if ($this->isValid()) { - $this->encrypt(); - } - } - } else { - if (! is_null($data) && ! is_null($key)) { - $this->setCipherText($data); - $this->setKey($key); - - if ($this->isValid()) { - $this->decrypt(); - } - } - } + return $this->isValid; } - abstract public function isValid(): bool; + /** + * Set the is valid status + * + * @param bool $isValid + * @return void + */ + public function setIsValid(bool $isValid): void + { + $this->isValid = $isValid; + } /** * Method to get current process @@ -81,7 +115,7 @@ public function getProcess(): string /** * Set the current process * - * @param string process + * @param string $process * @return void */ public function setProcess(string $process): void @@ -94,7 +128,7 @@ public function setProcess(string $process): void * * @return string */ - public function getPlainText(): string|null + public function getPlainText(): string { return $this->plainText; } @@ -102,7 +136,7 @@ public function getPlainText(): string|null /** * Set the plain text/message/data to be encrypted * - * @param string message + * @param string $plainText * @return void */ public function setPlainText(string $plainText): void @@ -123,7 +157,7 @@ public function getKey(): string /** * Set the key to be be used in encryption/decryption process * - * @param string key + * @param string $key * @return void */ public function setKey(string $key): void @@ -138,7 +172,7 @@ public function setKey(string $key): void * * @return string */ - public function getCipherText(): string|null + public function getCipherText(): string { return $this->cipherText; } @@ -146,7 +180,7 @@ public function getCipherText(): string|null /** * Set the cipher text result from encryption process * - * @param string message + * @param string $cipherText * @return void */ public function setCipherText(string $cipherText): void @@ -162,7 +196,7 @@ public function setCipherText(string $cipherText): void * Key: abcd (4 characters) * Repeated key: abcdabcdabcdab (14 characters) * - * @param string key + * @param string $key * @return string */ public function generateKey(string $key): string @@ -172,7 +206,7 @@ public function generateKey(string $key): string $this->cipherText); $repeatTimes = floor($messageLength / $keyLength); - $paddingKeyLength = $messageLength - ($keyLength * $repeatTimes); + $paddingKeyLength = (int) ($messageLength - ($keyLength * $repeatTimes)); $repeatedKey = ''; diff --git a/source/exceptions/InvalidAlnumException.php b/source/exceptions/InvalidAlnumException.php index a550793..ae12372 100644 --- a/source/exceptions/InvalidAlnumException.php +++ b/source/exceptions/InvalidAlnumException.php @@ -3,7 +3,8 @@ class InvalidAlnumException extends \Exception { - public function errorMessage() { + public function errorMessage(): string + { $message = 'Error on line ' . $this->getLine() . ' in ' . $this->getFile() . ': '. PHP_EOL; $message .= $this->getMessage() . ' is invalid, must be combination of a-z, A-Z, and 0-9!' . PHP_EOL; diff --git a/source/exceptions/InvalidBasicException.php b/source/exceptions/InvalidBasicException.php index 70e293a..e4dea0d 100644 --- a/source/exceptions/InvalidBasicException.php +++ b/source/exceptions/InvalidBasicException.php @@ -3,7 +3,8 @@ class InvalidBasicException extends \Exception { - public function errorMessage() { + public function errorMessage(): string + { $message = 'Error on line ' . $this->getLine() . ' in ' . $this->getFile() . ': '. PHP_EOL; $message .= $this->getMessage() . ' is invalid, must be combination of a-z!' . PHP_EOL; diff --git a/tests/VIgenereCipherTest.php b/tests/VIgenereCipherTest.php index fb9befb..f12ae37 100644 --- a/tests/VIgenereCipherTest.php +++ b/tests/VIgenereCipherTest.php @@ -15,14 +15,11 @@ public function testCanEncryptInBasicMode():void $data = 'encryptionprocess'; $key = 'thekey'; - $encrypted = VigenereCipher::encrypt($data, $key, VigenereMode::BASIC->value); + $encrypted = (string) VigenereCipher::encrypt($data, $key, VigenereMode::BASIC->value); $this->assertEquals(strlen($data), strlen($encrypted)); $this->assertEquals('xugbcnmpsxtphjicw', $encrypted); $this->assertNotEquals($data, $encrypted); - $this->assertIsString($data); - $this->assertIsString($key); - $this->assertIsString($encrypted); $this->assertMatchesRegularExpression($allowedChars, $data); $this->assertMatchesRegularExpression($allowedChars, $key); $this->assertMatchesRegularExpression($allowedChars, $encrypted); @@ -35,7 +32,7 @@ public function testCanNotEncryptInBasicModeWithInvalidKey():void $encrypted = VigenereCipher::encrypt($data, $key, VigenereMode::BASIC->value); - $this->assertNull($encrypted); + $this->assertEquals('', $encrypted); } public function testCanDecryptInBasicMode():void @@ -44,14 +41,11 @@ public function testCanDecryptInBasicMode():void $data = 'xugbcnmpsxtphjicw'; $key = 'thekey'; - $decrypted = VigenereCipher::decrypt($data, $key, VigenereMode::BASIC->value); + $decrypted = (string) VigenereCipher::decrypt($data, $key, VigenereMode::BASIC->value); $this->assertEquals(strlen($data), strlen($decrypted)); $this->assertEquals('encryptionprocess', $decrypted); $this->assertNotEquals($data, $decrypted); - $this->assertIsString($data); - $this->assertIsString($key); - $this->assertIsString($decrypted); $this->assertMatchesRegularExpression($allowedChars, $data); $this->assertMatchesRegularExpression($allowedChars, $key); $this->assertMatchesRegularExpression($allowedChars, $decrypted); @@ -64,7 +58,7 @@ public function testCanNotDecryptInBasicModeWithInvalidKey():void $encrypted = VigenereCipher::decrypt($data, $key, VigenereMode::BASIC->value); - $this->assertNull($encrypted); + $this->assertEquals('', $encrypted); } public function testCanEncryptInAlphaNumericMode():void @@ -78,9 +72,6 @@ public function testCanEncryptInAlphaNumericMode():void $this->assertEquals(strlen($data), strlen($encrypted)); $this->assertEquals('Xu5B2NMpTxjPHJWCz', $encrypted); $this->assertNotEquals($data, $encrypted); - $this->assertIsString($data); - $this->assertIsString($key); - $this->assertIsString($encrypted); $this->assertMatchesRegularExpression($allowedChars, $data); $this->assertMatchesRegularExpression($allowedChars, $key); $this->assertMatchesRegularExpression($allowedChars, $encrypted); @@ -93,7 +84,7 @@ public function testCanNotEncryptInAlphaNumericModeWithInvalidKey():void $encrypted = VigenereCipher::encrypt($data, $key, VigenereMode::ALPHA_NUMERIC->value); - $this->assertNull($encrypted); + $this->assertEquals('', $encrypted); } public function testCanDecryptWithAlphaNumericMode():void @@ -107,9 +98,6 @@ public function testCanDecryptWithAlphaNumericMode():void $this->assertEquals(strlen($data), strlen($decrypted)); $this->assertEquals('Encrypti0nProC3s5', $decrypted); $this->assertNotEquals($data, $decrypted); - $this->assertIsString($data); - $this->assertIsString($key); - $this->assertIsString($decrypted); $this->assertMatchesRegularExpression($allowedChars, $data); $this->assertMatchesRegularExpression($allowedChars, $key); $this->assertMatchesRegularExpression($allowedChars, $decrypted); @@ -122,7 +110,7 @@ public function testCanNotDecryptInAlphaNumericModeWithInvalidKey():void $encrypted = VigenereCipher::decrypt($data, $key, VigenereMode::ALPHA_NUMERIC->value); - $this->assertNull($encrypted); + $this->assertEquals('', $encrypted); } public function testCanGetBasicVigenereClass(): void @@ -132,7 +120,6 @@ public function testCanGetBasicVigenereClass(): void $className = VigenereCipher::getClassName($mode); - $this->assertIsString($className); $this->assertEquals($path . 'BasicVigenereCipher', $className); } @@ -143,7 +130,6 @@ public function testCanGetAlphaNumericVigenereClass(): void $className = VigenereCipher::getClassName($mode); - $this->assertIsString($className); $this->assertEquals($path . 'AlnumVigenereCipher', $className); } }