Simple cipher in php

Ok, I give up.
I’m comfortable with javascript, php is a new language for me.
The thing is, I don’t understand what these tests want. For example, I have to throw exceptions but do I have to exit the exercise, fix the issue… ? I got as far as 11 passed tests and 5 fails and then I revert back to 16 fails and I just don’t understand why. I don’t understand why I’m passing or failing.
The encoding and decoding functions are far from perfect but I don’t feel that that is the core issue, it’s exception handling and classes. I think. Tried downloading exercism, that worked in the past but reinstalled and got new laptop and blablabla. Can’t do it. So, I copy the exercise, I’ve got xampp running and there I can get rid of all the error messages and produce all the echo’s and var_dumps and see what I’m doing and why it’s happening. Then I copy everything back into exercism and … I’m very very tired.
Please help.
Thank you,
Karin

If you share your code and the test outputs, someone might be able to help explain what they are saying :slight_smile:

Please do not use images. Please use codeblocks.

Hi,

Thank you for this very quick reply and of course some code is convenient. I’m posting the whole thing. Thank you very much for taking an interest.

declare(strict_types=1);

class SimpleCipher
{

    public $alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];

    public function __construct(string $key = null )
    {
        $this->key = $key;
        $this->ensureKeyIntegrity();
 
    }
    public function ensureKeyIntegrity() {
        if($this->key == null) {
            echo "You have not entered a key.";
            throw new InvalidArgumentException("You have not entered a key.");                 
        }
        elseif(is_string($this->key) && strlen($this->$key) == 0) {
            echo "You have not entered a key.";
            throw new InvalidArgumentException("You haven't entered a text yet.");
        }
        $arr = str_split($this->key);
        foreach($arr as &$char) {
            if(ctype_upper($char)) {
                echo "uppercase: " .  $char . "\n";
                throw new InvalidArgumentException("All characters must be lower case.");             
            } 
            elseif(is_numeric($char)) {
                echo "number: " .  $char . "\n";
                throw new InvalidArgumentException("Numbers are not allowed.");     
            }
        }                    
    }
    public function createNewKey() {
        $index = rand(0, 25); 
        $newkey = $this->alphabet[$index];
        $this->key = $newkey;
    }
    public function encode(string $plainText): string
    { 
        $this->ensureKeyIntegrity();
        $arr = str_split($plainText);

        $lengteKey = strlen($this->key);
        $lengteText = strlen($plainText);
        if($lengteKey < $lengteText) {
            for($x=0, $aantal = $lengteText - $lengteKey; $x <= $aantal; $x++) {
                $this->key .= $this->key[$lengteKey-1];
            }
        }
        
        foreach($arr as $index => &$charact) {
            echo 'key index: ' . $this->key[$index] . '<br>';
            $encodingInt = array_search($this->key[$index], $this->alphabet);
            echo 'encodingInt: ' . $encodingInt . '<br>';
            if((ord($charact) + $encodingInt) > 122 ) {
                $charact = ord($charact) - $encodingInt;
            } else {
                $charact = ord($charact) + $encodingInt;
            }
            $charact= chr($charact);
            echo $charact . '<br>';
        }
        $encodedText = implode($arr);
        echo "encodedText " . $encodedText . '<br>';
        return $encodedText;
    }

    public function decode(string $cipherText): string
    {
        $arr = str_split($cipherText);
        $arr = str_split($cipherText);
        foreach($arr as $index => &$charact) {
            echo 'key index: ' . $this->key[$index] . '<br>';
            $decodingInt = array_search($this->key[$index], $this->alphabet);
            echo 'decodingInt: ' . $decodingInt . '<br>';
            if((ord($charact) - $decodingInt) < 0 ) {
                $charact = ord($charact) + $decodingInt;
            } else {
                $charact = ord($charact) - $decodingInt;
            }
            $charact= chr($charact);
            echo $charact . '<br>';
        }
        $decodedText = implode($arr);        
        var_dump("decodedText " . $decodedText);
        return $decodedText;
    }
}

Greets,
Kairn

Could you also share the test outputs? What test fails? What is the code run and failure message?

Hi @mientje, welcome back :slight_smile:

I have helped you get over some obstacles some time ago, and I’m glad you got along further on the track.

Take a close look at this line. Do you see the place, where you don’t access the property correctly? Fixing this makes your code work again.

Some of the tests create a new instance of your class without providing a key, for example this one:

    public function testRandomCipherKeyIsLetters(): void
    {
        $cipher = new SimpleCipher();
        $this->assertMatchesRegularExpression('/\A[a-z]+\z/', $cipher->key);
    }

The test name should tell you, that for no key given you have to make one yourself, using a source of randomness. PHP has some functions for that, but none of them produces an output directly that would match the regular expression. So you need to write one that produces a valid key.

When you get stuck again or have further questions, we are here to help.

Hi,

I apologise for this very late reply. I’ve been sick, coughing, wheezing, such fatigue… and then there were Christmas gifts, Christmas dinner, Christmas guests…

I haven’t solved it yet but I don’t feel stuck any more. The first suggestion helps me to pass ten tests and the second inspires me to try out a few things.

So thank you very much for that. Will get back to you.

Have a great 2025.

Greets,
Karin

1 Like

On my own machine everything works but I’m not passing the tests that state I need to assert invalidArgumentExceptions. I can, though, print all the exceptions. I think I am throwing them, cathing them and echoing them.

I don’t understand what tests two and three are about.

$cipher = new SimpleCipher();
$plaintext = 'aaaaaaaaaa';
$this->assertEquals(substr($cipher->key, 0, 10), $cipher->encode($plaintext));

I believe I’m seeing a substr that is ten characters long and that key should be equal to the plaintext? I don’t get this at all

Something similar must be going on in test three :

$cipher = new SimpleCipher();
$plaintext = 'aaaaaaaaaa';
$this->assertEquals($plaintext, $cipher->decode(substr($cipher->key, 0, 10)));

This is my code:

class SimpleCipher
{
    
    public $alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];

    public function __construct(string $key = null ) {
        $this->ensureKeyIntegrity($key);
    }

    public function lowerCase($char) : string {
        try {
            if(ctype_upper($char)) {
                echo "All characters must be lower case.\n";
                throw new InvalidArgumentException("All characters must be lower case.");             
            }     
        }
        catch(InvalidArgumentException $e) {
            echo $e;
            $char = strtolower($char);                
        }
        return $char;
    }

    public function numericStrings($char) : string {
        try {
            if(is_numeric($char)) {
                echo "Numbers are not allowed.\n";
                throw new InvalidArgumentException("Numbers are not allowed.");     
            }
        }
        catch(InvalidArgumentException $e) {
            //echo $e;
            $char = $this->createNewKey();
        }   
        return $char;             
    }

    public function ensureKeyIntegrity($key) {
        try {
            if($key != null) {
                try {
                    if(is_string($key) && is_string($key) && strlen($key) > 0) {
                        $arr = str_split($key); 
                        foreach($arr as &$char) { 
                            $char =  $this->lowerCase($char);
                            $char = $this->numericStrings($char);  
                        }        
                        $this->key = implode($arr);
                    } 
                    else {
                        echo "You cannot enter empty spaces.\n";
                        throw new InvalidArgumentException("Empty strings are not allowed.");    
                    }       
                }
                catch(invalidArgumentException $b) {
                    //echo $b;
                    $this->key = $this->createNewKey();    
                }    
            } else {
                echo "You have not entered a key.\n";
                throw new InvalidArgumentException("You have not entered a key.");    
            }            
        }
        catch(invalidArgumentException $a) {
            echo $a;
            $this->key = $this->createNewKey();    
        }    
        return $this->key;
    }

    public function createNewKey() : string {
        $index = rand(0, 25); 
        $newkey = $this->alphabet[$index];
        return $newkey;
    }

    public function adjustKeyLength($textArr) {
        $lengteKeys = strlen($this->key);
        $lengteText = count($textArr);
        if($lengteKeys < $lengteText) {
            for($x=0, $aantal = $lengteText - $lengteKeys; $x <= $aantal; $x++) {
                $this->key .= $this->createNewKey();
            }
        } 
    }
    
    public function encode(string $plainText): string  { 
        $arr = str_split($plainText);
        $this->adjustKeyLength($arr);
        $keys = str_split($this->key);
        foreach($arr as $index => &$charact) { 
            $encodingInt = array_search($keys[$index], $this->alphabet);          
            $characterInt = array_search($charact, $this->alphabet); 
            $encodedcharact = $characterInt + $encodingInt;                
            if($encodedcharact > 25 ) {
                $encodedcharact -= 26;
            }
            $charact= $this->alphabet[$encodedcharact];
        }        
        $encodedText = implode($arr);
        echo "encodedText " . $encodedText . "\n";
        return $encodedText;
    }

    public function decode(string $cipherText): string {
        $arr = str_split($cipherText);
        $keys = str_split($this->key);
        foreach($arr as $index => &$charact) { 
            $decodingInt = array_search($keys[$index], $this->alphabet);      
            $characterInt = array_search($charact, $this->alphabet);
            $decodedcharact = $characterInt - $decodingInt;    
            if($decodedcharact < 0 ) {
                $decodedcharact += 26;
            }
            $charact= $this->alphabet[$decodedcharact];
        }   
        $decodedText = implode($arr);        
        echo "decodedText " . $decodedText . "\n";
        return $decodedText;
    }
}

Could you explain to me what tests 2 and three mean and why I’m not passing the invalidArgumentExceptions tests?

Thank you and greets,
Karin

Your code is throwing an invalid argument exception. The tests expect a default key to be used and for the encode/decode to work.

Hi,

So I don’t need to throw exceptions? I have completely misunderstood those tests then.

I think my key does work. Uppercase letters are converted to lowercase letters, numbers are replaced by a random key, empty strings and no content is replaced by a default key that I generate.

I generate it before the encoding function is called and I adjust the length of that key to the plaintext argument in the encoding function.
What am I doing wrong then?

Greets,
Karin

This particular test expects to get a string from your function. Instead, your function (the constructor) is throwing an exception and not returning a string. That’s what this code is doing wrong.

Hi,

What particular test exactly ? I still don’t see that. I suspect this is not so much a programming problem as a misinterpretation of the instructions. I just don’t see it. As I undestand it there has to be one function that generates a string that serves as a key and if it doesn’t an exception is thrown. I think I have done that in ensureKeyIntegrity. In that function I produce an (altered) or randomly generated key. I don’t understand when exactly I’m not returning the string and I really don’t understand what test 2 and 3 are about.

Greets,
Karin

Could you provide (a complete copy of) the latest iteration of your code along with the complete text of a failing test (in a codeblock, not an image)? That would give us a specific, concrete test to discuss :slight_smile:

Below is the most recent version.

Btw, probably completely irrelevant, but I work in exercism in windows and in ubuntu on the same laptop. The most recent version is on the windows side, I’m now working in ubuntu because I don’t have word and I’m taking notes on a php course on youtube “programming with gio”. Excellent course.

class SimpleCipher
{
    
    public $alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];

    public function __construct(string $key = null ) {
        $this->ensureKeyIntegrity($key);
    }

    public function lowerCase($char) : string {
        try {
            if(ctype_upper($char)) {
                echo "All characters must be lower case.\n";
                throw new InvalidArgumentException("All characters must be lower case.");             
            }     
        }
        catch(InvalidArgumentException $e) {
            echo $e;
            $char = strtolower($char);                
        }
        return $char;
    }

    public function numericStrings($char) : string {
        try {
            if(is_numeric($char)) {
                echo "Numbers are not allowed.\n";
                throw new InvalidArgumentException("Numbers are not allowed.");     
            }
        }
        catch(InvalidArgumentException $e) {
            //echo $e;
            $char = $this->createNewKey();
        }   
        return $char;             
    }

    public function ensureKeyIntegrity($key) {
        try {
            if($key != null) {
                try {
                    if(is_string($key) && is_string($key) && strlen($key) > 0) {
                        $arr = str_split($key); 
                        foreach($arr as &$char) { 
                            $char =  $this->lowerCase($char);
                            $char = $this->numericStrings($char);  
                        }        
                        $this->key = implode($arr);
                    } 
                    else {
                        echo "You cannot enter empty spaces.\n";
                        throw new InvalidArgumentException("Empty strings are not allowed.");    
                    }       
                }
                catch(invalidArgumentException $b) {
                    //echo $b;
                    $this->key = $this->createNewKey();    
                }    
            } else {
                echo "You have not entered a key.\n";
                throw new InvalidArgumentException("You have not entered a key.");    
            }            
        }
        catch(invalidArgumentException $a) {
            echo $a;
            $this->key = $this->createNewKey();    
        }    
        return $this->key;
    }

    public function createNewKey() : string {
        $index = rand(0, 25); 
        $newkey = $this->alphabet[$index];
        return $newkey;
    }

    public function adjustKeyLength($textArr) {
        $lengteKeys = strlen($this->key);
        $lengteText = count($textArr);
        if($lengteKeys < $lengteText) {
            for($x=0, $aantal = $lengteText - $lengteKeys; $x <= $aantal; $x++) {
                $this->key .= $this->createNewKey();
            }
        } 
    }
    
    public function encode(string $plainText): string  { 
        $arr = str_split($plainText);
        $this->adjustKeyLength($arr);
        $keys = str_split($this->key);
        foreach($arr as $index => &$charact) { 
            $encodingInt = array_search($keys[$index], $this->alphabet);          
            $characterInt = array_search($charact, $this->alphabet); 
            $encodedcharact = $characterInt + $encodingInt;                
            if($encodedcharact > 25 ) {
                $encodedcharact -= 26;
            }
            $charact= $this->alphabet[$encodedcharact];
        }        
        $encodedText = implode($arr);
        echo "encodedText " . $encodedText . "\n";
        return $encodedText;
    }

    public function decode(string $cipherText): string {
        $arr = str_split($cipherText);
        $keys = str_split($this->key);
        foreach($arr as $index => &$charact) { 
            $decodingInt = array_search($keys[$index], $this->alphabet);      
            $characterInt = array_search($charact, $this->alphabet);
            $decodedcharact = $characterInt - $decodingInt;    
            if($decodedcharact < 0 ) {
                $decodedcharact += 26;
            }
            $charact= $this->alphabet[$decodedcharact];
        }   
        $decodedText = implode($arr);        
        echo "decodedText " . $decodedText . "\n";
        return $decodedText;
    }
}

What tests fail for that code? What does the test details show?

Tests 2, 3, 5, 6 and 7

test 2 random key cipher encode

Code Run

$cipher = new SimpleCipher();
$plaintext = 'aaaaaaaaaa';
$this->assertEquals(substr($cipher->key, 0, 10), $cipher->encode($plaintext));

Test Failure

SimpleCipherTest::testRandomKeyCipherEncode
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'m'
+'murwiojebj'

SimpleCipherTest.php:49

test 3 random key cipher decode

Code Run

$cipher = new SimpleCipher();
$plaintext = 'aaaaaaaaaa';
$this->assertEquals($plaintext, $cipher->decode(substr($cipher->key, 0, 10)));

Test Failure

SimpleCipherTest::testRandomKeyCipherDecode
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'aaaaaaaaaa'
+'a'

SimpleCipherTest.php:56

test 5 Cipher with caps key

Code Run

$this->expectException(\InvalidArgumentException::class);
$cipher = new SimpleCipher('ABCDEF');

Test Failure

SimpleCipherTest::testCipherWithCapsKey Failed asserting that exception of type “InvalidArgumentException” is thrown.

test 6 Cipher with numeric key

Code Run

$this->expectException(\InvalidArgumentException::class);
$cipher = new SimpleCipher('12345');

Test Failure

SimpleCipherTest::testCipherWithNumericKey Failed asserting that exception of type “InvalidArgumentException” is thrown.

test 7 cipher with empty key

Code Run

$this->expectException(\InvalidArgumentException::class);
$cipher = new SimpleCipher('');

Test Failure

SimpleCipherTest::testCipherWithEmptyKey Failed asserting that exception of type “InvalidArgumentException” is thrown.

Thank you!
Greets,
Karin

Tests 2 and 3 seem to be getting back one character when they expect a 10 character string.

Tests 5, 6, 7 expect InvalidArgumentException to be thrown but that doesn’t happen.

Hi,

Yes but in tests 2 and 3 the key seems to have to be equal to the encoded text. And there is this substring with a length of ten characters.
assertEquals(substr($cipher=>key, 0, 10). Why should the key have to be equal to the encoded text and how can they expect one character then? And why is only one character expected? I don’t understand what is being tested. The same problem for test three.

In tests 5, 6 and 7 I am throwing these exceptions myself. I can echo them. So does this mean that I should not throw them? The system has to throw them? I don’t understand the point of these tests either. Maybe it’s staring me in the face (probably) but I really don’t see it.

Thanks and greets,
Karin

'a' has a “offset” of 0. When you encode 'aaa' using key 'key', the result should be "key". Tests 2 and 3 just test that property.

$cipher->encode('aaaaaaaaaa')) should give the key. Or, rather, the first 10 characters of the key, whatever the key might be.

For tests 5, 6, 7, you throw the exception … then catch it. The exception doesn’t “leave” the function. If you catch the exception in the same function that throws it, from the perspective of the caller, it never got thrown.

Ok, I’m going to munch on that.
Thank you and will get back to you.

Greets,
Karin

Note, tests 2 and 3 seem to work just fine … for the first character. The encoding is working, just not encoding enough.