Skip to content

Commit 7fe950d

Browse files
SNO7E-Gclaude
andauthored
Modernize codebase to PHP 8.2+ with strict types and CI matrix (#190)
- Require PHP ^8.2; upgrade PHPUnit to ^10.5 || ^11.0 - Replace the PHP 7.4 CI job with an 8.2-8.5 matrix using per-version Composer cache keys - Enforce declare(strict_types=1) and PSR-12 across the codebase; convert legacy switch statements to match expressions; remove dead code - Fix latent float-division bugs with intdiv() in DecimalToBinary, DecimalToOctal, DecimalToHex, RailfenceCipher, MergeSort, and HeapSort - ArrayHelpers: throw new \UnexpectedValueException() (previously an undefined-constant fatal) and fix the sortedness check (&& to ||) - median(): declare float|int return type and reject non-numeric input (behavior change: previously returned a meaningless value, now throws) - Stack::search(): return int|false, mirroring array_search() - Queue::toString() and maxCharacter(): cast to string to preserve behavior under strict types - Add regression tests for each fix; suite green on PHP 8.2-8.5 (285 tests, 4856 assertions) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 81b5386 commit 7fe950d

125 files changed

Lines changed: 961 additions & 883 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@ jobs:
1010
build:
1111

1212
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
php-version: ['8.2', '8.3', '8.4', '8.5']
1316

1417
steps:
1518
- uses: actions/checkout@v4
1619

1720
- name: Setup PHP
1821
uses: shivammathur/setup-php@v2
1922
with:
20-
php-version: '7.4'
23+
php-version: ${{ matrix.php-version }}
2124
ini-values: xdebug.max_nesting_level=512
2225

2326
- name: Validate composer.json and composer.lock
@@ -28,13 +31,13 @@ jobs:
2831
uses: actions/cache@v3
2932
with:
3033
path: vendor
31-
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
34+
key: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}
3235
restore-keys: |
33-
${{ runner.os }}-php-
36+
${{ runner.os }}-php-${{ matrix.php-version }}-
3437
3538
- name: Install dependencies
3639
if: steps.composer-cache.outputs.cache-hit != 'true'
37-
run: composer install --prefer-dist --no-progress --no-suggest
40+
run: composer install --prefer-dist --no-progress
3841

3942
- name: Run PHPUnit
40-
run: composer run-script test
43+
run: composer run-script test

.github/workflows/code-style.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ jobs:
1616
- name: Setup PHP
1717
uses: shivammathur/setup-php@v2
1818
with:
19-
php-version: '7.4'
19+
php-version: '8.2'
2020

2121
- name: Install dependencies
22-
run: composer update --prefer-dist --no-progress --no-suggest
22+
run: composer update --prefer-dist --no-progress
2323

2424
- name: Run script
2525
run: vendor/bin/phpcs -n

CONTRIBUTING.md

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,31 +28,33 @@ Please help us keep our issue list small by adding fixes: #{$ISSUE_NO} to the co
2828
#### What is an Algorithm?
2929

3030
An Algorithm is one or more functions (or classes) that:
31-
* take one or more inputs,
32-
* perform some internal calculations or data manipulations,
33-
* return one or more outputs,
34-
* have minimal side effects (Ex. print(), plot(), read(), write()).
31+
32+
- take one or more inputs,
33+
- perform some internal calculations or data manipulations,
34+
- return one or more outputs,
35+
- have minimal side effects (Ex. print(), plot(), read(), write()).
3536

3637
Algorithms should be packaged in a way that would make it easy for readers to put them into larger programs.
3738

3839
Algorithms should:
39-
* have intuitive class and function names that make their purpose clear to readers
40-
* use PHP naming conventions and intuitive variable names to ease comprehension
41-
* be flexible to take different input values
42-
* have PHP type hints for their input parameters and return values
43-
* raise PHP exceptions (UnexpectedValueException, etc.) on erroneous input values
44-
* have docstrings with clear explanations and/or URLs to source materials
45-
* contain doctests that test both valid and erroneous input values
46-
* return all calculation results instead of printing or plotting them
40+
41+
- have intuitive class and function names that make their purpose clear to readers
42+
- use PHP naming conventions and intuitive variable names to ease comprehension
43+
- be flexible to take different input values
44+
- have PHP type hints for their input parameters and return values
45+
- raise PHP exceptions (UnexpectedValueException, etc.) on erroneous input values
46+
- have docstrings with clear explanations and/or URLs to source materials
47+
- contain doctests that test both valid and erroneous input values
48+
- return all calculation results instead of printing or plotting them
4749

4850
Algorithms in this repo should not be how-to examples for existing PHP packages. Instead, they should perform internal calculations or manipulations to convert input values into different output values. Those calculations or manipulations can use data types, classes, or functions of existing PHP packages but each algorithm in this repo should add unique value.
4951

5052
#### Coding Style
5153

5254
We want your work to be readable by others; therefore, we encourage you to note the following:
5355

54-
- Please write in PHP 7.1+
55-
- Please put thought into naming of functions, classes, and variables. Help your reader by using __descriptive names__ that can help you to remove redundant comments
56+
- Please write in PHP 8.2+
57+
- Please put thought into naming of functions, classes, and variables. Help your reader by using **descriptive names** that can help you to remove redundant comments
5658
- Single letter variable names are _old school_ so please avoid them unless their life only spans a few lines
5759
- Please follow the [PHP Basic Coding Standard](https://www.php-fig.org/psr/psr-12/) style guide. So functionNames should be camelCase, CONSTANTS in UPPER_CASE, Name\Spaces and ClassNames should follow an "autoloading" PSR, etc.
5860

@@ -66,13 +68,14 @@ We want your work to be readable by others; therefore, we encourage you to note
6668

6769
- Avoid importing external libraries for basic algorithms. Only use them for complicated algorithms
6870

69-
- Ensure code is linted with phpcs, and passing all linting checks (vendor/bin/phpcs -n)
71+
- Ensure code is strictly typed and passes the repository test suite (`composer run-script test`)
72+
- Ensure code is properly linted and formatted (`vendor/bin/phpcs -n`)
7073

7174
#### Other Standard While Submitting Your Work
7275

73-
- File extension for code should be `.php`
76+
- File extension for code should be `.php`
7477
- After adding a new File/Directory, please make sure to update the [DIRECTORY.md](DIRECTORY.md) file with the details.
75-
- If possible, follow the standard *within* the folder you are submitting to
78+
- If possible, follow the standard _within_ the folder you are submitting to
7679
- If you have modified/added code work, make sure the code compiles before submitting
7780
- If you have modified/added documentation work, ensure your language is concise and contains no grammar errors
7881
- Add a corresponding explanation to [Algorithms-Explanation](https://github.com/TheAlgorithms/Algorithms-Explanation) (Optional but recommended).

Ciphers/AtbashCipher.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
/**
46
* Encrypt a message using the Atbash Cipher.
57
* The Atbash Cipher is a simple substitution cipher where each letter in the plaintext is
@@ -9,7 +11,7 @@
911
* @param string $plainText The plaintext to encrypt.
1012
* @return string The encrypted message.
1113
*/
12-
function atbash_encrypt($plainText)
14+
function atbash_encrypt($plainText): string
1315
{
1416
$result = '';
1517
$plainText = strtoupper($plainText);
@@ -21,8 +23,10 @@ function atbash_encrypt($plainText)
2123
} else {
2224
$encryptedChar = $char; // Non-alphabet characters remain unchanged
2325
}
26+
2427
$result .= $encryptedChar;
2528
}
29+
2630
return $result;
2731
}
2832

@@ -33,7 +37,7 @@ function atbash_encrypt($plainText)
3337
* @param string $cipherText The ciphertext to decrypt.
3438
* @return string The decrypted message.
3539
*/
36-
function atbash_decrypt($cipherText)
40+
function atbash_decrypt($cipherText): string
3741
{
3842
return atbash_encrypt($cipherText); // Decryption is the same as encryption
3943
}

Ciphers/CaesarCipher.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
/**
46
* Encrypt given text using caesar cipher.
57
*
@@ -41,6 +43,7 @@ function decrypt(string $text, int $shift): string
4143
if ($placeValue < 0) { // Handling case where remainder is negative
4244
$placeValue += 26;
4345
}
46+
4447
$placeValue += ord(ctype_upper($c) ? 'A' : 'a');
4548
$newChar = chr($placeValue); // Getting new character from new value (i.e. A-Z)
4649
$decryptedText .= $newChar; // Appending decrypted character

Ciphers/MonoAlphabeticCipher.php

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,39 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
// A mono-alphabetic cipher is a simple substitution cipher
46
// https://www.101computing.net/mono-alphabetic-substitution-cipher/
57

6-
function monoAlphabeticCipher($key, $alphabet, $text)
8+
function monoAlphabeticCipher(string $key, $alphabet, $text): false|string
79
{
810
$cipherText = ''; // the cipher text (can be decrypted and encrypted)
911

1012
// check if the text length matches
11-
if (strlen($key) != strlen($alphabet)) {
13+
if (strlen($key) !== strlen((string) $alphabet)) {
1214
return false;
1315
}
1416

15-
$text = preg_replace('/[0-9]+/', '', $text); // remove all the numbers
17+
$text = preg_replace('/\d+/', '', (string) $text); // remove all the numbers
1618

17-
for ($i = 0; $i < strlen($text); $i++) {
18-
$index = strripos($alphabet, $text[$i]);
19-
if ($text[$i] == " ") {
19+
for ($i = 0; $i < strlen((string) $text); $i++) {
20+
$index = strripos((string) $alphabet, $text[$i]);
21+
if ($text[$i] === " ") {
2022
$cipherText .= " ";
2123
} else {
22-
$cipherText .= ( ctype_upper($text[$i]) ? strtoupper($key[$index]) : $key[$index] );
24+
$cipherText .= (ctype_upper($text[$i]) ? strtoupper($key[$index]) : $key[$index]);
2325
}
2426
}
2527

2628
return $cipherText;
2729
}
2830

29-
function maEncrypt($key, $alphabet, $text)
31+
function maEncrypt($key, $alphabet, $text): string|false
3032
{
3133
return monoAlphabeticCipher($key, $alphabet, $text);
3234
}
3335

34-
function maDecrypt($key, $alphabet, $text)
36+
function maDecrypt($key, $alphabet, $text): string|false
3537
{
3638
return monoAlphabeticCipher($alphabet, $key, $text);
3739
}

Ciphers/MorseCode.php

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
/**
46
* Encode text to Morse Code.
57
*
@@ -10,7 +12,8 @@
1012
function encode(string $text): string
1113
{
1214
$text = strtoupper($text); // Makes sure the string is uppercase
13-
$MORSE_CODE = array( // Array containing morse code translations
15+
$MORSE_CODE = [
16+
// Array containing morse code translations
1417
"A" => ".-",
1518
"B" => "-...",
1619
"C" => "-.-.",
@@ -47,17 +50,18 @@ function encode(string $text): string
4750
"8" => "---..",
4851
"9" => "----.",
4952
"0" => "-----",
50-
" " => "/"
51-
);
53+
" " => "/",
54+
];
5255

5356
$encodedText = ""; // Stores the encoded text
5457
foreach (str_split($text) as $c) { // Going through each character
5558
if (array_key_exists($c, $MORSE_CODE)) { // Checks if it is a valid character
5659
$encodedText .= $MORSE_CODE[$c] . " "; // Appends the correct character
5760
} else {
58-
throw new \Exception("Invalid character: $c");
61+
throw new \Exception('Invalid character: ' . $c);
5962
}
6063
}
64+
6165
substr_replace($encodedText, "", -1); // Removes trailing space
6266
return $encodedText;
6367
}
@@ -69,7 +73,8 @@ function encode(string $text): string
6973
*/
7074
function decode(string $text): string
7175
{
72-
$MORSE_CODE = array( // An array containing morse code to text translations
76+
$MORSE_CODE = [
77+
// An array containing morse code to text translations
7378
".-" => "A",
7479
"-..." => "B",
7580
"-.-." => "C",
@@ -106,18 +111,20 @@ function decode(string $text): string
106111
"---.." => "8",
107112
"----." => "9",
108113
"-----" => "0",
109-
"/" => " "
110-
);
114+
"/" => " ",
115+
];
111116

112117
$decodedText = ""; // Stores the decoded text
113118
foreach (explode(" ", $text) as $c) { // Going through each group
114-
if (array_key_exists($c, $MORSE_CODE)) { // Checks if it is a valid character
115-
$decodedText .= $MORSE_CODE[$c]; // Appends the correct character
116-
} else {
117-
if ($c) { // Makes sure that the string is not empty to prevent trailing spaces or extra spaces from breaking this
118-
throw new \Exception("Invalid character: $c");
119-
}
119+
if (array_key_exists($c, $MORSE_CODE)) {
120+
// Checks if it is a valid character
121+
$decodedText .= $MORSE_CODE[$c];
122+
// Appends the correct character
123+
} elseif ($c !== '' && $c !== '0') {
124+
// Makes sure that the string is not empty to prevent trailing spaces or extra spaces from breaking this
125+
throw new \Exception('Invalid character: ' . $c);
120126
}
121127
}
128+
122129
return $decodedText;
123130
}

Ciphers/RailfenceCipher.php

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
/**
46
* Encode a message using the Rail Fence Cipher.
57
* (https://en.wikipedia.org/wiki/Rail_fence_cipher)
@@ -19,15 +21,17 @@ function Railencode($plainMessage, $rails): string
1921
if (!isset($cipherMessage[$step])) {
2022
$cipherMessage[$step] = '';
2123
}
24+
2225
// Check if the character should go in the rail
23-
if ($index % $position == $step || $index % $position == $position - $step) {
26+
if ($index % $position === $step || $index % $position == $position - $step) {
2427
$cipherMessage[$step] .= $plainMessage[$index];
2528
} else {
2629
// Add a placeholder for empty spaces
2730
$cipherMessage[$step] .= ".";
2831
}
2932
}
3033
}
34+
3135
// Combine and remove placeholders to form the cipher message
3236
return implode('', str_replace('.', '', $cipherMessage));
3337
}
@@ -44,7 +48,7 @@ function Raildecode($cipherMessage, $rails): string
4448
{
4549
$position = ($rails * 2) - 2;
4650
$textLength = strlen($cipherMessage);
47-
$minLength = floor($textLength / $position);
51+
$minLength = intdiv($textLength, $position);
4852
$balance = $textLength % $position;
4953
$lengths = [];
5054
$strings = [];
@@ -55,27 +59,29 @@ function Raildecode($cipherMessage, $rails): string
5559
if ($rowIndex != 0 && $rowIndex != ($rails - 1)) {
5660
$lengths[$rowIndex] += $minLength;
5761
}
62+
5863
if ($balance > $rowIndex) {
5964
$lengths[$rowIndex]++;
6065
}
66+
6167
if ($balance > ($rails + ($rails - $rowIndex) - 2)) {
6268
$lengths[$rowIndex]++;
6369
}
70+
6471
$strings[] = substr($cipherMessage, $totalLengths, $lengths[$rowIndex]);
6572
$totalLengths += $lengths[$rowIndex];
6673
}
74+
6775
// Convert the rows of characters to plain message
6876
$plainText = '';
6977
while (strlen($plainText) < $textLength) {
7078
for ($charIndex = 0; $charIndex < $position; $charIndex++) {
71-
if (isset($strings[$charIndex])) {
72-
$index = $charIndex;
73-
} else {
74-
$index = $position - $charIndex;
75-
}
79+
$index = isset($strings[$charIndex]) ? $charIndex : $position - $charIndex;
80+
7681
$plainText .= substr($strings[$index], 0, 1);
7782
$strings[$index] = substr($strings[$index], 1);
7883
}
7984
}
85+
8086
return $plainText;
8187
}

Ciphers/VignereCipher.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
/**
46
* Encrypts a plaintext using the Vigenère cipher.
57
* (https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher)
@@ -29,6 +31,7 @@ function vigenere_encrypt($plaintext, $key): string
2931
$encryptedText .= $char;
3032
}
3133
}
34+
3235
return $encryptedText;
3336
}
3437

@@ -59,5 +62,6 @@ function vigenere_decrypt($ciphertext, $key): string
5962
$decryptedText .= $char;
6063
}
6164
}
65+
6266
return $decryptedText;
6367
}

0 commit comments

Comments
 (0)