···11+# Exercism Exercises
22+33+A collection of my solutions to exercises completed on the [Exercism](https://exercism.org) platform.
44+55+## About Exercism
66+77+Exercism is a free, open-source platform that offers code practice and mentorship across dozens of programming language tracks. Each track focuses on a specific programming language and provides a set of exercises ranging from beginner to advanced level. Exercises can be completed locally using the Exercism CLI and submitted for community or mentor feedback.
88+99+## Repository Structure
1010+1111+Exercises are organized by language track. Each track folder contains a `flake.nix` file that provides a Nix development shell with all the necessary tooling to work on exercises locally, along with one subdirectory per completed exercise.
1212+1313+Some files and folders such as `.exercism` and `HELP.md` are ignored because they are identical across every exercise and are not relevant to reading the solutions.
1414+1515+```
1616+.
1717+├── zig/
1818+│ ├── flake.nix
1919+│ ├── hello-world/
2020+│ └── ...
2121+├── php/
2222+│ ├── flake.nix
2323+│ ├── hello-world/
2424+│ └── ...
2525+```
2626+2727+## Development Environment
2828+2929+Each language folder contains a `flake.nix` that sets up a ready-to-use development shell via Nix. This ensures a reproducible environment without manually installing compilers, runtimes, or package managers on the host system.
3030+3131+To enter the development shell for a given track, run the following from inside the track folder:
3232+3333+```bash
3434+nix develop
3535+```
3636+3737+This requires [Nix](https://nixos.org/download) to be installed with flakes enabled.
3838+3939+## Purpose
4040+4141+This repository serves as a personal archive of progress made on the platform. It is not intended as a reference for official solutions and reflects my own learning process.
4242+4343+## Missing Exercises
4444+4545+Some exercises cannot be downloaded to be completed locally. All solutions, including those done directly on the platform, can be found on my profile: [cosmeak](https://exercism.org/profiles/cosmeak).
+8
php/city-office/Address.php
···11+<?php
22+33+class Address
44+{
55+ public string $street;
66+ public string $postal_code;
77+ public string $city;
88+}
+232
php/city-office/CityOfficeTest.php
···11+<?php
22+33+use PHPUnit\Framework\TestCase;
44+use PHPUnit\Framework\Attributes\TestDox;
55+66+require_once 'ReflectionAssertions.php';
77+require_once 'Address.php';
88+require_once 'Form.php';
99+1010+class CityOfficeTest extends TestCase
1111+{
1212+ use ReflectionAssertions;
1313+1414+ /**
1515+ * @task_id 1
1616+ */
1717+ #[TestDox('specify a string type for Address::$street')]
1818+ public function testTypeOfAddressStreetProperty()
1919+ {
2020+ $this->assertProperty(
2121+ class_name: Address::class,
2222+ property_name: 'street',
2323+ assertions: [
2424+ 'has_type' => true,
2525+ 'type_name' => 'string',
2626+ 'type_allows_null' => false
2727+ ]
2828+ );
2929+ }
3030+3131+ /**
3232+ * @task_id 1
3333+ */
3434+ #[TestDox('specify a string type for Address::$postal_code')]
3535+ public function testTypeOfAddressPostalCodeProperty()
3636+ {
3737+ $this->assertProperty(
3838+ class_name: Address::class,
3939+ property_name: 'postal_code',
4040+ assertions: [
4141+ 'has_type' => true,
4242+ 'type_name' => 'string',
4343+ 'type_allows_null' => false
4444+ ]
4545+ );
4646+ }
4747+4848+ /**
4949+ * @task_id 1
5050+ */
5151+ #[TestDox('specify a string type for Address::$city')]
5252+ public function testTypeOfAddressCityProperty()
5353+ {
5454+ $this->assertProperty(
5555+ class_name: Address::class,
5656+ property_name: 'city',
5757+ assertions: [
5858+ 'has_type' => true,
5959+ 'type_name' => 'string',
6060+ 'type_allows_null' => false
6161+ ]
6262+ );
6363+ }
6464+6565+ /**
6666+ * @task_id 2
6767+ */
6868+ #[TestDox('specify an int type for Form::blanks length parameter')]
6969+ public function testParameterTypeOfFormBlanksLengthParameter()
7070+ {
7171+ $this->assertMethodParameter(
7272+ class_name: Form::class,
7373+ method_name: 'blanks',
7474+ parameter_name: 'length',
7575+ parameter_index: 0,
7676+ assertions: [
7777+ 'has_type' => true,
7878+ 'type_name' => 'int',
7979+ 'type_allows_null' => false,
8080+ 'has_default_value' => false
8181+ ]
8282+ );
8383+ }
8484+8585+ /**
8686+ * @task_id 2
8787+ */
8888+ #[TestDox('specify an int type for Form::blanks return type')]
8989+ public function testParameterTypeOfFormBlanksReturnType()
9090+ {
9191+ $this->assertMethodReturnType(
9292+ class_name: Form::class,
9393+ method_name: 'blanks',
9494+ assertions: [
9595+ 'has_type' => true,
9696+ 'type_name' => 'string',
9797+ 'type_allows_null' => false,
9898+ ]
9999+ );
100100+ }
101101+102102+ /**
103103+ * @task_id 3
104104+ */
105105+ #[TestDox('specify an int type for Form::letters word parameter')]
106106+ public function testParameterTypeOfFormLettersWordParameter()
107107+ {
108108+ $this->assertMethodParameter(
109109+ class_name: Form::class,
110110+ method_name: 'letters',
111111+ parameter_name: 'word',
112112+ parameter_index: 0,
113113+ assertions: [
114114+ 'has_type' => true,
115115+ 'type_name' => 'string',
116116+ 'type_allows_null' => false,
117117+ 'has_default_value' => false
118118+ ]
119119+ );
120120+ }
121121+122122+ /**
123123+ * @task_id 3
124124+ */
125125+ #[TestDox('specify an int type for Form::letters return type')]
126126+ public function testParameterTypeOfFormLettersReturnType()
127127+ {
128128+ $this->assertMethodReturnType(
129129+ class_name: Form::class,
130130+ method_name: 'letters',
131131+ assertions: [
132132+ 'has_type' => true,
133133+ 'type_name' => 'array',
134134+ 'type_allows_null' => false,
135135+ ]
136136+ );
137137+ }
138138+139139+ /**
140140+ * @task_id 4
141141+ */
142142+ #[TestDox('specify an int type for Form::checkLength word parameter')]
143143+ public function testParameterTypeOfFormCheckLengthWordParameter()
144144+ {
145145+ $this->assertMethodParameter(
146146+ class_name: Form::class,
147147+ method_name: 'checkLength',
148148+ parameter_name: 'word',
149149+ parameter_index: 0,
150150+ assertions: [
151151+ 'has_type' => true,
152152+ 'type_name' => 'string',
153153+ 'type_allows_null' => false,
154154+ 'has_default_value' => false
155155+ ]
156156+ );
157157+ }
158158+159159+ /**
160160+ * @task_id 4
161161+ */
162162+ #[TestDox('specify an int type for Form::checkLength max_length parameter')]
163163+ public function testParameterTypeOfFormCheckLengthMaxLengthParameter()
164164+ {
165165+ $this->assertMethodParameter(
166166+ class_name: Form::class,
167167+ method_name: 'checkLength',
168168+ parameter_name: 'max_length',
169169+ parameter_index: 1,
170170+ assertions: [
171171+ 'has_type' => true,
172172+ 'type_name' => 'int',
173173+ 'type_allows_null' => false,
174174+ 'has_default_value' => false
175175+ ]
176176+ );
177177+ }
178178+179179+ /**
180180+ * @task_id 4
181181+ */
182182+ #[TestDox('specify an int type for Form::checkLength return type')]
183183+ public function testParameterTypeOfFormCheckLengthReturnType()
184184+ {
185185+ $this->assertMethodReturnType(
186186+ class_name: Form::class,
187187+ method_name: 'checkLength',
188188+ assertions: [
189189+ 'has_type' => true,
190190+ 'type_name' => 'bool',
191191+ 'type_allows_null' => false,
192192+ ]
193193+ );
194194+ }
195195+196196+ /**
197197+ * @task_id 5
198198+ */
199199+ #[TestDox('specify an Address type for Form::formatAddress address parameter')]
200200+ public function testParameterTypeOfFormFormatAddressParameter()
201201+ {
202202+ $this->assertMethodParameter(
203203+ class_name: Form::class,
204204+ method_name: 'formatAddress',
205205+ parameter_name: 'address',
206206+ parameter_index: 0,
207207+ assertions: [
208208+ 'has_type' => true,
209209+ 'type_name' => 'Address',
210210+ 'type_allows_null' => false,
211211+ 'has_default_value' => false
212212+ ]
213213+ );
214214+ }
215215+216216+ /**
217217+ * @task_id 5
218218+ */
219219+ #[TestDox('specify an int type for Form::checkLength return type')]
220220+ public function testParameterTypeOfFormFormatAddressReturnType()
221221+ {
222222+ $this->assertMethodReturnType(
223223+ class_name: Form::class,
224224+ method_name: 'formatAddress',
225225+ assertions: [
226226+ 'has_type' => true,
227227+ 'type_name' => 'string',
228228+ 'type_allows_null' => false,
229229+ ]
230230+ );
231231+ }
232232+}
+102
php/city-office/README.md
···11+# City Office
22+33+Welcome to City Office on Exercism's PHP Track.
44+If you need help running the tests or submitting your code, check out `HELP.md`.
55+If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
66+77+## Introduction
88+99+## Type Declaration
1010+1111+Type declarations in PHP provide type assertions at run time for function arguments, return values and class properties.
1212+On functions, a `void` return type can be added to indicate that no value is returned from the function.
1313+Declared types also serve as run time assertions that functions are returning a reasonably typed response.
1414+1515+```php
1616+<?php
1717+1818+class Driver
1919+{
2020+ private int $serial_number;
2121+2222+ public function setSerialNumber(int $number): void
2323+ {
2424+ $this->serial_number = $number;
2525+ }
2626+2727+ public function getSerialNumber(): int
2828+ {
2929+ return $this->serial_number;
3030+ }
3131+}
3232+3333+$driver = new Driver();
3434+$driver->setSerialNumber("Version 1b"); // This will throw a TypeError
3535+```
3636+3737+### Type Unions
3838+3939+If a function argument, return value or class property can be more than one type a type union may be declared.
4040+4141+```php
4242+<?php
4343+4444+class IdentityCard
4545+{
4646+ private int|null $id = null;
4747+4848+ public function assign(int|float $id): void
4949+ {
5050+ $this->id = intval($id);
5151+ }
5252+}
5353+5454+$card = new IdentityCard();
5555+$card->assign(5.0);
5656+```
5757+5858+### Mixed Types
5959+6060+When working with code, we may not be able to specify a type.
6161+Starting in PHP 8.0,an escape-hatch type named `mixed` is provided.
6262+Mixed is equivalent to a type union of `object|resource|array|string|float|int|bool|null`.
6363+6464+## Instructions
6565+6666+You have been working in the city office for a while, and you have developed a set of tools that speed up your day-to-day work, for example with filling out forms.
6767+6868+Now, a new colleague is joining you, and you realized your tools might not be self-explanatory.
6969+There are a lot of weird conventions in your office, like always filling out forms with uppercase letters and avoiding leaving fields empty.
7070+7171+As a first step you decide to add PHP type declarations so that it is easier for your new colleague to hop right in and start using your tools.
7272+7373+## 1. Declare the types for the Address class
7474+7575+Add property type declarations to each of the `Address` class properties declared.
7676+Each class property should be declared as a string.
7777+7878+## 2. Declare the types to fill out the form with blank values
7979+8080+Add a parameter type declaration and a return type declaration to the `blanks` method in the `Form` class.
8181+The method should take in an integer length, and return a string representation of the blank line.
8282+8383+## 3. Declare the type when splitting a value into separate letters
8484+8585+Add a parameter type declaration and a return type declaration to the `letters` method in the `Form` class.
8686+The method should take in a string, representing words, and return an array of letters.
8787+8888+## 4. Declare the type when checking if a value will fit into a form
8989+9090+Add parameter type declarations and a return type declaration to the `checkLength` method in the `Form` class.
9191+The method should take in a string word, and an integer maximum length, and return a true or false value.
9292+9393+## 5. Declare the type when formatting an address in the form
9494+9595+Add a parameter type declaration, making use of the `Address` class updated previously, and a return type declaration to the `formatAddress` method in the `Form` class.
9696+The method should take in an `Address` and return a formatted string.
9797+9898+## Source
9999+100100+### Created by
101101+102102+- @neenjaw
···11+<?php
22+33+declare(strict_types=1);
44+55+function distance(string $strandA, string $strandB): int
66+{
77+ if (strlen($strandA) != strlen($strandB)) {
88+ throw new InvalidArgumentException("strands must be of equal length");
99+ }
1010+1111+ $distance = 0;
1212+1313+ for ($i = 0; $i < strlen($strandA); $i++) {
1414+ if ($strandA[$i] != $strandB[$i]) {
1515+ $distance++;
1616+ }
1717+ }
1818+1919+ return $distance;
2020+}
+109
php/hamming/HammingTest.php
···11+<?php
22+33+declare(strict_types=1);
44+55+use PHPUnit\Framework\TestCase;
66+use PHPUnit\Framework\Attributes\TestDox;
77+88+class HammingTest extends TestCase
99+{
1010+ public static function setUpBeforeClass(): void
1111+ {
1212+ require_once 'Hamming.php';
1313+ }
1414+1515+ /**
1616+ * uuid: f6dcb64f-03b0-4b60-81b1-3c9dbf47e887
1717+ */
1818+ #[TestDox('Empty strands')]
1919+ public function testEmptyStrands(): void
2020+ {
2121+ $this->assertEquals(0, distance('', ''));
2222+ }
2323+2424+ /**
2525+ * uuid: 54681314-eee2-439a-9db0-b0636c656156
2626+ */
2727+ #[TestDox('Single letter identical strands')]
2828+ public function testSingleLetterIdenticalStrands(): void
2929+ {
3030+ $this->assertEquals(0, distance('A', 'A'));
3131+ }
3232+3333+ /**
3434+ * uuid: 294479a3-a4c8-478f-8d63-6209815a827b
3535+ */
3636+ #[TestDox('Single letter different strands')]
3737+ public function testSingleLetterDifferentStrands(): void
3838+ {
3939+ $this->assertEquals(1, distance('G', 'T'));
4040+ }
4141+4242+ /**
4343+ * uuid: 9aed5f34-5693-4344-9b31-40c692fb5592
4444+ */
4545+ #[TestDox('Long identical strands')]
4646+ public function testLongIdenticalStrands(): void
4747+ {
4848+ $this->assertEquals(0, distance(
4949+ 'GGACTGAAATCTG',
5050+ 'GGACTGAAATCTG'
5151+ ));
5252+ }
5353+5454+ /**
5555+ * uuid: cd2273a5-c576-46c8-a52b-dee251c3e6e5
5656+ */
5757+ #[TestDox('Long different strands')]
5858+ public function testLongDifferentStrand(): void
5959+ {
6060+ $this->assertEquals(9, distance(
6161+ 'GGACGGATTCTG',
6262+ 'AGGACGGATTCT'
6363+ ));
6464+ }
6565+6666+ /**
6767+ * uuid: b9228bb1-465f-4141-b40f-1f99812de5a8
6868+ */
6969+ #[TestDox('Disallow first strand longer')]
7070+ public function testDisallowFirstStrandLonger(): void
7171+ {
7272+ $this->expectException('InvalidArgumentException');
7373+ $this->expectExceptionMessage('strands must be of equal length');
7474+ distance('AATG', 'AAA');
7575+ }
7676+7777+ /**
7878+ * uuid: dab38838-26bb-4fff-acbe-3b0a9bfeba2d
7979+ */
8080+ #[TestDox(': Disallow second strand longer')]
8181+ public function testDisallowSecondStrandLonger(): void
8282+ {
8383+ $this->expectException('InvalidArgumentException');
8484+ $this->expectExceptionMessage('strands must be of equal length');
8585+ distance('ATA', 'AATG');
8686+ }
8787+8888+ /**
8989+ * uuid: b764d47c-83ff-4de2-ab10-6cfe4b15c0f3
9090+ */
9191+ #[TestDox(': Disallow empty first strand')]
9292+ public function testDisallowEmptyFirstStrand(): void
9393+ {
9494+ $this->expectException('InvalidArgumentException');
9595+ $this->expectExceptionMessage('strands must be of equal length');
9696+ distance('', 'G');
9797+ }
9898+9999+ /**
100100+ * uuid: 9ab9262f-3521-4191-81f5-0ed184a5aa89
101101+ */
102102+ #[TestDox(': Disallow empty second strand')]
103103+ public function testDisallowEmptySecondStrand(): void
104104+ {
105105+ $this->expectException('InvalidArgumentException');
106106+ $this->expectExceptionMessage('strands must be of equal length');
107107+ distance('G', '');
108108+ }
109109+}
+53
php/hamming/README.md
···11+# Hamming
22+33+Welcome to Hamming on Exercism's PHP Track.
44+If you need help running the tests or submitting your code, check out `HELP.md`.
55+66+## Introduction
77+88+Your body is made up of cells that contain DNA.
99+Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells.
1010+In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime!
1111+1212+When cells divide, their DNA replicates too.
1313+Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information.
1414+If we compare two strands of DNA and count the differences between them, we can see how many mistakes occurred.
1515+This is known as the "Hamming distance".
1616+1717+The Hamming distance is useful in many areas of science, not just biology, so it's a nice phrase to be familiar with :)
1818+1919+## Instructions
2020+2121+Calculate the Hamming distance between two DNA strands.
2222+2323+We read DNA using the letters C, A, G and T.
2424+Two strands might look like this:
2525+2626+ GAGCCTACTAACGGGAT
2727+ CATCGTAATGACGGCCT
2828+ ^ ^ ^ ^ ^ ^^
2929+3030+They have 7 differences, and therefore the Hamming distance is 7.
3131+3232+## Implementation notes
3333+3434+The Hamming distance is only defined for sequences of equal length, so an attempt to calculate it between sequences of different lengths should not work.
3535+3636+## Source
3737+3838+### Contributed to by
3939+4040+- @arueckauer
4141+- @dkinzer
4242+- @Dog
4343+- @kip-13
4444+- @kunicmarko20
4545+- @kytrinyx
4646+- @lafent
4747+- @marvinrabe
4848+- @petemcfarlane
4949+- @rossbearman
5050+5151+### Based on
5252+5353+The Calculating Point Mutations problem at Rosalind - https://rosalind.info/problems/hamm/
···11+# Hello World
22+33+Welcome to Hello World on Exercism's PHP Track.
44+If you need help running the tests or submitting your code, check out `HELP.md`.
55+66+## Instructions
77+88+The classical introductory exercise.
99+Just say "Hello, World!".
1010+1111+["Hello, World!"][hello-world] is the traditional first program for beginning programming in a new language or environment.
1212+1313+The objectives are simple:
1414+1515+- Modify the provided code so that it produces the string "Hello, World!".
1616+- Run the test suite and make sure that it succeeds.
1717+- Submit your solution and check it at the website.
1818+1919+If everything goes well, you will be ready to fetch your first real exercise.
2020+2121+[hello-world]: https://en.wikipedia.org/wiki/%22Hello,_world!%22_program
2222+2323+## Source
2424+2525+### Created by
2626+2727+- @duffn
2828+2929+### Contributed to by
3030+3131+- @arueckauer
3232+- @joseph-walker
3333+- @kytrinyx
3434+- @petemcfarlane
3535+3636+### Based on
3737+3838+This is an exercise to introduce users to using Exercism - https://en.wikipedia.org/wiki/%22Hello,_world!%22_program
···11+<?php
22+33+use PHPUnit\Framework\TestCase;
44+use PHPUnit\Framework\Attributes\TestDox;
55+66+class LanguageListTest extends TestCase
77+{
88+ public static function setUpBeforeClass(): void
99+ {
1010+ require_once 'LanguageList.php';
1111+ }
1212+1313+ /**
1414+ * @task_id 1
1515+ */
1616+ #[TestDox('calling language_list without arguments, returns empty array')]
1717+ public function testEmpty()
1818+ {
1919+ $language_list = language_list();
2020+ $this->assertEquals([], $language_list);
2121+ }
2222+2323+ /**
2424+ * @task_id 2
2525+ */
2626+ #[TestDox('variadic call to language_list with 1 arg returns args')]
2727+ public function testVariadicCallWithOne()
2828+ {
2929+ $language_list = language_list('c');
3030+ $this->assertEquals(['c'], $language_list);
3131+ }
3232+3333+ /**
3434+ * @task_id 2
3535+ */
3636+ #[TestDox('variadic call to language_list with 2 arg returns args')]
3737+ public function testVariadicCallWithTwo()
3838+ {
3939+ $language_list = language_list('c', 'cpp');
4040+ $this->assertEquals(['c', 'cpp'], $language_list);
4141+ }
4242+4343+ /**
4444+ * @task_id 2
4545+ */
4646+ #[TestDox('variadic call to language_list with 2 arg returns args')]
4747+ public function testVariadicCallWithThree()
4848+ {
4949+ $language_list = language_list('c', 'cpp', 'php');
5050+ $this->assertEquals(['c', 'cpp', 'php'], $language_list);
5151+ }
5252+5353+ /**
5454+ * @task_id 3
5555+ */
5656+ #[TestDox('push new languages to the back of the list')]
5757+ public function testAddingToLanguageList()
5858+ {
5959+ $language_list = language_list('c', 'cpp', 'php');
6060+ $pushed_list = add_to_language_list($language_list, 'java');
6161+ $this->assertEquals(['c', 'cpp', 'php', 'java'], $pushed_list);
6262+ }
6363+6464+ /**
6565+ * @task_id 3
6666+ */
6767+ #[TestDox('when pushing, original is unchanged')]
6868+ public function testAddingDoesNotMutate()
6969+ {
7070+ $language_list = language_list('c', 'cpp', 'php');
7171+ $pushed_list = add_to_language_list($language_list, 'java');
7272+ $this->assertNotEquals($language_list, $pushed_list);
7373+ }
7474+7575+ /**
7676+ * @task_id 4
7777+ */
7878+ #[TestDox('remove completed language from the front of the list')]
7979+ public function testCompleteLanguageList()
8080+ {
8181+ $language_list = language_list('c', 'cpp', 'php');
8282+ $pruned_list = prune_language_list($language_list);
8383+ $this->assertEquals(['cpp', 'php'], $pruned_list);
8484+ }
8585+8686+ /**
8787+ * @task_id 4
8888+ */
8989+ #[TestDox('when pushing, original is unchanged')]
9090+ public function testPruningDoesNotMutate()
9191+ {
9292+ $language_list = language_list('c', 'cpp', 'php');
9393+ $pruned_list = prune_language_list($language_list);
9494+ $this->assertNotEquals($language_list, $pruned_list);
9595+ }
9696+9797+ /**
9898+ * @task_id 5
9999+ */
100100+ #[TestDox('index and return the first language')]
101101+ public function testCurrentReturnsTheFirstLanguage()
102102+ {
103103+ $language_list = language_list('php');
104104+ $current = current_language($language_list);
105105+ $this->assertEquals('php', $current);
106106+ }
107107+108108+ /**
109109+ * @task_id 5
110110+ */
111111+ #[TestDox('when getting the first language, original is unchanged')]
112112+ public function testGettingFirstDoesNotMutate()
113113+ {
114114+ $language_list = language_list('c', 'cpp', 'php');
115115+ current_language($language_list);
116116+ $this->assertEquals(['c', 'cpp', 'php'], $language_list);
117117+ }
118118+119119+ /**
120120+ * @task_id 6
121121+ */
122122+ #[TestDox('the count of the languages in the language list')]
123123+ public function testLanguageListCount()
124124+ {
125125+ $language_list = language_list('c', 'cpp', 'php');
126126+ $languages_count = language_list_length($language_list);
127127+ $this->assertEquals(3, $languages_count);
128128+ }
129129+130130+ /**
131131+ * @task_id 6
132132+ */
133133+ #[TestDox('when getting the language count, original is unchanged')]
134134+ public function testLanguageListCountDoesNotMutate()
135135+ {
136136+ $language_list = language_list('c', 'cpp', 'php');
137137+ language_list_length($language_list);
138138+ $this->assertCount(3, $language_list);
139139+ }
140140+}
+147
php/language-list/README.md
···11+# Language List
22+33+Welcome to Language List on Exercism's PHP Track.
44+If you need help running the tests or submitting your code, check out `HELP.md`.
55+If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
66+77+## Introduction
88+99+## Arrays
1010+1111+Arrays in PHP are ordered maps.
1212+A map is a data structure that associates a key to a value.
1313+Arrays are versatile and can be treated as a linear array (like in `C` or `Java`), list [vector], hash-table, dictionary, collection, stack, or a queue.
1414+1515+A key is optional, but if specified must either be an `int` or a `string`.
1616+When a key is not provided, PHP defaults to integer keys in increasing order, starting at `0`.
1717+1818+```php
1919+$no_keys = ["my", "first", "array"];
2020+$integer_keys = [0 => "my", 1 => "first", 2 => "array"];
2121+2222+$no_keys == $integer_keys // => equal returns true
2323+$no_keys === $integer_keys // => strictly equal returns true
2424+```
2525+2626+An array can store values of all types.
2727+Each value can have a different type.
2828+2929+### Using Arrays
3030+3131+Arrays can be declared as a literal (written in code, as done above) or created and manipulated as functions.
3232+Access, assign, append values using the index operator:
3333+3434+```php
3535+$prime_numbers = [2, 3, 5, 6];
3636+3737+$prime_numbers[3] = 7; // replace 6 with 7
3838+3939+$prime_numbers[] = 11; // array now contains [2, 3, 5, 7, 11]
4040+```
4141+4242+## Variable-Length Arguments
4343+4444+Function arguments can be specified such that it can take any number of arguments:
4545+4646+```php
4747+<?php
4848+4949+function actOnItems(...$items) {
5050+ // $items is an array containing 0 or more values
5151+ // used to call the function.
5252+}
5353+5454+actOnItems(1, 2, 3, 4); // $items => [1, 2, 3, 4]
5555+```
5656+5757+## Instructions
5858+5959+In this exercise you need to implement some functions to manipulate a list of programming languages.
6060+6161+## 1. Define a function to return an empty language list
6262+6363+Define the `language_list` function that takes no arguments and returns an empty list.
6464+6565+```php
6666+<?php
6767+6868+language_list();
6969+// => []
7070+```
7171+7272+## 2. Modify function to create a list from any number of languages
7373+7474+Modify the `language_list` function, so it takes a variadic argument of languages (strings).
7575+It should return the resulting list with the languages in the list.
7676+7777+```php
7878+<?php
7979+8080+$language_list = language_list();
8181+// => []
8282+$language_list = language_list("Clojure", "PHP");
8383+// => ["Clojure", "PHP"]
8484+$language_list = language_list("PHP", "Haskell", "Java", "C++", "Rust")
8585+// => ["PHP", "Haskell", "Java", "C++", "Rust"]
8686+```
8787+8888+## 3. Define a function to add a language to the list
8989+9090+Define the `add_to_language_list` function that takes 2 arguments, an array of languages, and the new language.
9191+9292+```php
9393+<?php
9494+9595+$language_list = language_list();
9696+// => []
9797+$language_list = add_to_language_list($language_list, "Clojure");
9898+// => ["Clojure"]
9999+```
100100+101101+## 4. Define a function to remove an item from the language list
102102+103103+Define the `prune_language_list` function to remove the first language from the array of languages.
104104+105105+```php
106106+<?php
107107+108108+$language_list = language_list("PHP");
109109+// => ["PHP"]
110110+$language_list = prune_language_list($language_list);
111111+// => []
112112+```
113113+114114+## 5. Define a function to get the first item in the list
115115+116116+Define the `current_language` function that takes 1 argument (a _language list_).
117117+It should return the first language in the list.
118118+Assume the list will always have at least one item.
119119+120120+```php
121121+<?php
122122+123123+$language_list = language_list("PHP", "Prolog");
124124+// => ["PHP", "Prolog"]
125125+$first = current_language($language_list);
126126+// => "PHP"
127127+```
128128+129129+## 6. Define a function to return how many languages are in the list
130130+131131+Define the `language_list_length` function that takes 1 argument (a _language list_).
132132+It should return the number of languages in the list.
133133+134134+```php
135135+<?php
136136+137137+$language_list = language_list("PHP", "Prolog", "Wren");
138138+// => ["PHP", "Prolog", "Wren"]
139139+language_list_length($language_list);
140140+// => 3
141141+```
142142+143143+## Source
144144+145145+### Created by
146146+147147+- @neenjaw
+29
php/lasagna/Lasagna.php
···11+<?php
22+33+class Lasagna
44+{
55+ public function expectedCookTime(): int
66+ {
77+ return 40;
88+ }
99+1010+ public function remainingCookTime($elapsed_minutes): int
1111+ {
1212+ return $this->expectedCookTime() - $elapsed_minutes;
1313+ }
1414+1515+ public function totalPreparationTime($layers_to_prep): int
1616+ {
1717+ return $layers_to_prep * 2;
1818+ }
1919+2020+ public function totalElapsedTime($layers_to_prep, $elapsed_minutes): int
2121+ {
2222+ return $this->totalPreparationTime($layers_to_prep) + ($this->expectedCookTime() - $this->remainingCookTime($elapsed_minutes));
2323+ }
2424+2525+ public function alarm(): string
2626+ {
2727+ return "Ding!";
2828+ }
2929+}
+86
php/lasagna/LasagnaTest.php
···11+<?php
22+33+declare(strict_types=1);
44+55+use PHPUnit\Framework\TestCase;
66+use PHPUnit\Framework\Attributes\TestDox;
77+88+/**
99+ * We use `assertEquals()` here for its loose type checking. We don't care if
1010+ * the student returns numeric strings or integers in this exercise.
1111+ *
1212+ * - Please use `assertSame()` whenever possible. Add a comment when it is not possible.
1313+ * - Do not use calls with named arguments.
1414+ * Use them only when the exercise requires named arguments (e.g. because the exercise is about named arguments).
1515+ * Named arguments are in the way of defining argument names the students want (e.g. in their native language).
1616+ * - Add `#[TestDox()]` with a useful test title, e.g. the task heading from `instructions.md`.
1717+ * The online editor shows that to students.
1818+ * - Add fail messages to assertions where helpful to tell students more than `#[TestDox()]` says.
1919+ */
2020+class LasagnaTest extends TestCase
2121+{
2222+ public static function setUpBeforeClass(): void
2323+ {
2424+ require_once 'Lasagna.php';
2525+ }
2626+2727+ /**
2828+ * @task_id 1
2929+ */
3030+ #[TestDox('Returns cooking time in minutes as stated in the cook book')]
3131+ public function testExpectedCookTime(): void
3232+ {
3333+ $lasagna = new Lasagna();
3434+ $this->assertEquals(40, $lasagna->expectedCookTime());
3535+ }
3636+3737+ /**
3838+ * @task_id 2
3939+ */
4040+ #[TestDox('Returns how many minutes more the lasagna must be in the oven when it is 20 minutes in the oven already')]
4141+ public function testRemainingCookTime(): void
4242+ {
4343+ $lasagna = new Lasagna();
4444+ $this->assertEquals(20, $lasagna->remainingCookTime(20));
4545+ }
4646+4747+ /**
4848+ * @task_id 2
4949+ */
5050+ #[TestDox('Returns how many minutes more the lasagna must be in the oven when it is 30 minutes in the oven already')]
5151+ public function testAnotherRemainingCookTime(): void
5252+ {
5353+ $lasagna = new Lasagna();
5454+ $this->assertEquals(10, $lasagna->remainingCookTime(30));
5555+ }
5656+5757+ /**
5858+ * @task_id 3
5959+ */
6060+ #[TestDox('Returns how many minutes you spent preparing the lasagna with 7 layers')]
6161+ public function testTotalPreparationTime(): void
6262+ {
6363+ $lasagna = new Lasagna();
6464+ $this->assertEquals(14, $lasagna->totalPreparationTime(7));
6565+ }
6666+6767+ /**
6868+ * @task_id 4
6969+ */
7070+ #[TestDox('Returns the total minutes you have worked on the lasagna with 4 layers that is 13 minutes in the oven')]
7171+ public function testTotalElapsedTime(): void
7272+ {
7373+ $lasagna = new Lasagna();
7474+ $this->assertEquals(21, $lasagna->totalElapsedTime(4, 13));
7575+ }
7676+7777+ /**
7878+ * @task_id 5
7979+ */
8080+ #[TestDox('Returns the message indicating that the lasagna is ready to eat')]
8181+ public function testAlarm(): void
8282+ {
8383+ $lasagna = new Lasagna();
8484+ $this->assertEquals("Ding!", $lasagna->alarm());
8585+ }
8686+}
+158
php/lasagna/README.md
···11+# Lucian's Luscious Lasagna
22+33+Welcome to Lucian's Luscious Lasagna on Exercism's PHP Track.
44+If you need help running the tests or submitting your code, check out `HELP.md`.
55+If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
66+77+## Introduction
88+99+## Basics
1010+1111+For more detailed information about these topics visit the [concept page][exercism-concept].
1212+1313+### General syntax
1414+1515+The PHP opening tag `<?php` marks the start of PHP code.
1616+All statements must end with a `;` for instruction separation.
1717+1818+```php
1919+<?php
2020+2121+$message = "Success!"; // Statement correctly ends with `;`
2222+$message = "I fail." // PHP Parse error: syntax error, [...]
2323+```
2424+2525+For easier reading, all code examples omit the PHP opening tag `<?php`.
2626+2727+### Comments
2828+2929+Single line comments start with `//`.
3030+3131+```php
3232+// Single line comment
3333+```
3434+3535+### Values and Variables
3636+3737+Using the assignment `=` operator, a value may be assigned to a variable.
3838+Variable names must start with a dollar `$` sign and follow the [naming conventions][exercism-concept-naming-conventions].
3939+4040+```php
4141+$count = 1; // Assign value of 1
4242+4343+// Strings can be created by enclosing the characters
4444+// within single `'` quotes or double `"` quotes.
4545+$message = "Success!";
4646+```
4747+4848+### Functions and Methods
4949+5050+Values and variables can be passed to functions.
5151+Function arguments become new variables to hold values passed in.
5252+Functions may return any value using the keyword `return`.
5353+5454+```php
5555+function window_height($height)
5656+{
5757+ return $height + 10;
5858+}
5959+6060+window_height(100);
6161+// => 110
6262+```
6363+6464+Functions inside classes and their instances are called methods.
6565+To call a method, the name is preceded by the instance and `->`.
6666+Methods have access to the special variable `$this`, which refers to the current instance.
6767+6868+```php
6969+<?php
7070+7171+class Calculator {
7272+ public function sub($x, $y)
7373+ {
7474+ return $this->add($x, -$y); // Calls the method add() of the current instance
7575+ }
7676+7777+ public function add($x, $y)
7878+ {
7979+ return $x + $y;
8080+ }
8181+}
8282+8383+$calculator = new Calculator(); // Creates a new instance of Calculator class
8484+$calculator->sub(3, 1); // Calls the method sub() of the instance stored in $calculator
8585+// => 2
8686+```
8787+8888+[exercism-concept]: /tracks/php/concepts/basic-syntax
8989+[exercism-concept-naming-conventions]: /tracks/php/concepts/basic-syntax#h-naming-conventions
9090+9191+## Instructions
9292+9393+In this exercise you're going to write some code to help you cook a brilliant lasagna from your favorite cooking book.
9494+9595+You have five tasks, all related to the time spent cooking the lasagna.
9696+9797+## 1. Define the expected oven time in minutes
9898+9999+Implement the `expectedCookTime` function in class `Lasagna` that does not take any arguments and returns how many minutes the lasagna should be in the oven.
100100+According to the cooking book, the expected oven time in minutes is 40:
101101+102102+```php
103103+<?php
104104+$timer = new Lasagna();
105105+$timer->expectedCookTime()
106106+// => 40
107107+```
108108+109109+## 2. Calculate the remaining oven time in minutes
110110+111111+Implement the `remainingCookTime` function in class `Lasagna` that takes the actual minutes the lasagna has been in the oven as an argument and returns how many minutes the lasagna still has to remain in the oven, based on the expected oven time in minutes from the previous task.
112112+113113+```php
114114+<?php
115115+$timer = new Lasagna();
116116+$timer->remainingCookTime(30)
117117+// => 10
118118+```
119119+120120+## 3. Calculate the preparation time in minutes
121121+122122+Implement the `totalPreparationTime` function in class `Lasagna` that takes the number of layers you added to the lasagna as an argument and returns how many minutes you spent preparing the lasagna, assuming each layer takes you 2 minutes to prepare.
123123+124124+```php
125125+<?php
126126+$timer = new Lasagna();
127127+$timer->totalPreparationTime(3)
128128+// => 6
129129+```
130130+131131+## 4. Calculate the total working time in minutes
132132+133133+Implement the `totalElapsedTime` function in class `Lasagna` that takes two arguments: the first argument is the number of layers you added to the lasagna, and the second argument is the number of minutes the lasagna has been in the oven.
134134+The function should return how many minutes in total you've worked on cooking the lasagna, which is the sum of the preparation time in minutes, and the time in minutes the lasagna has spent in the oven at the moment.
135135+136136+```php
137137+<?php
138138+$timer = new Lasagna();
139139+$timer->totalElapsedTime(3, 20)
140140+// => 26
141141+```
142142+143143+## 5. Create a notification that the lasagna is ready
144144+145145+Implement the `alarm` function in class `Lasagna` that does not take any arguments and returns a message indicating that the lasagna is ready to eat.
146146+147147+```php
148148+<?php
149149+$timer = new Lasagna();
150150+$timer->alarm()
151151+// => "Ding!"
152152+```
153153+154154+## Source
155155+156156+### Created by
157157+158158+- @neenjaw
···11+<?php
22+33+declare(strict_types=1);
44+55+use PHPUnit\Framework\TestCase;
66+use PHPUnit\Framework\Attributes\TestDox;
77+88+final class LineUpTest extends TestCase
99+{
1010+ public static function setUpBeforeClass(): void
1111+ {
1212+ require_once('LineUp.php');
1313+ }
1414+1515+ /** uuid: 7760d1b8-4864-4db4-953b-0fa7c047dbc0 */
1616+ #[TestDox('Format smallest non-exceptional ordinal numeral 4')]
1717+ public function testFormatSmallestNonExceptionalOrdinalNumeral4(): void
1818+ {
1919+ $this->assertSame(
2020+ 'Gianna, you are the 4th customer we serve today. Thank you!',
2121+ format('Gianna', 4)
2222+ );
2323+ }
2424+2525+ /** uuid: e8b7c715-6baa-4f7b-8fb3-2fa48044ab7a */
2626+ #[TestDox('Format greatest single digit non-exceptional ordinal numeral 9')]
2727+ public function testFormatGreatestSingleDigitNonExceptionalOrdinalNumeral9(): void
2828+ {
2929+ $this->assertSame(
3030+ 'Maarten, you are the 9th customer we serve today. Thank you!',
3131+ format('Maarten', 9)
3232+ );
3333+ }
3434+3535+ /** uuid: f370aae9-7ae7-4247-90ce-e8ff8c6934df */
3636+ #[TestDox('Format non-exceptional ordinal numeral 5')]
3737+ public function testFormatNonExceptionalOrdinalNumeral5(): void
3838+ {
3939+ $this->assertSame(
4040+ 'Petronila, you are the 5th customer we serve today. Thank you!',
4141+ format('Petronila', 5)
4242+ );
4343+ }
4444+4545+ /** uuid: 37f10dea-42a2-49de-bb92-0b690b677908 */
4646+ #[TestDox('Format non-exceptional ordinal numeral 6')]
4747+ public function testFormatNonExceptionalOrdinalNumeral6(): void
4848+ {
4949+ $this->assertSame(
5050+ 'Attakullakulla, you are the 6th customer we serve today. Thank you!',
5151+ format('Attakullakulla', 6)
5252+ );
5353+ }
5454+5555+ /** uuid: d8dfb9a2-3a1f-4fee-9dae-01af3600054e */
5656+ #[TestDox('Format non-exceptional ordinal numeral 7')]
5757+ public function testFormatNonExceptionalOrdinalNumeral7(): void
5858+ {
5959+ $this->assertSame(
6060+ 'Kate, you are the 7th customer we serve today. Thank you!',
6161+ format('Kate', 7)
6262+ );
6363+ }
6464+6565+ /** uuid: 505ec372-1803-42b1-9377-6934890fd055 */
6666+ #[TestDox('Format non-exceptional ordinal numeral 8')]
6767+ public function testFormatNonExceptionalOrdinalNumeral8(): void
6868+ {
6969+ $this->assertSame(
7070+ 'Maximiliano, you are the 8th customer we serve today. Thank you!',
7171+ format('Maximiliano', 8)
7272+ );
7373+ }
7474+7575+ /** uuid: 8267072d-be1f-4f70-b34a-76b7557a47b9 */
7676+ #[TestDox('Format exceptional ordinal numeral 1')]
7777+ public function testFormatExceptionalOrdinalNumeral1(): void
7878+ {
7979+ $this->assertSame(
8080+ 'Mary, you are the 1st customer we serve today. Thank you!',
8181+ format('Mary', 1)
8282+ );
8383+ }
8484+8585+ /** uuid: 4d8753cb-0364-4b29-84b8-4374a4fa2e3f */
8686+ #[TestDox('Format exceptional ordinal numeral 2')]
8787+ public function testFormatExceptionalOrdinalNumeral2(): void
8888+ {
8989+ $this->assertSame(
9090+ 'Haruto, you are the 2nd customer we serve today. Thank you!',
9191+ format('Haruto', 2)
9292+ );
9393+ }
9494+9595+ /** uuid: 8d44c223-3a7e-4f48-a0ca-78e67bf98aa7 */
9696+ #[TestDox('Format exceptional ordinal numeral 3')]
9797+ public function testFormatExceptionalOrdinalNumeral3(): void
9898+ {
9999+ $this->assertSame(
100100+ 'Henriette, you are the 3rd customer we serve today. Thank you!',
101101+ format('Henriette', 3)
102102+ );
103103+ }
104104+105105+ /** uuid: 6c4f6c88-b306-4f40-bc78-97cdd583c21a */
106106+ #[TestDox('Format smallest two digit non-exceptional ordinal numeral 10')]
107107+ public function testFormatSmallestTwoDigitNonExceptionalOrdinalNumeral10(): void
108108+ {
109109+ $this->assertSame(
110110+ 'Alvarez, you are the 10th customer we serve today. Thank you!',
111111+ format('Alvarez', 10)
112112+ );
113113+ }
114114+115115+ /** uuid: e257a43f-d2b1-457a-97df-25f0923fc62a */
116116+ #[TestDox('Format non-exceptional ordinal numeral 11')]
117117+ public function testFormatNonExceptionalOrdinalNumeral11(): void
118118+ {
119119+ $this->assertSame(
120120+ 'Jacqueline, you are the 11th customer we serve today. Thank you!',
121121+ format('Jacqueline', 11)
122122+ );
123123+ }
124124+125125+ /** uuid: bb1db695-4d64-457f-81b8-4f5a2107e3f4 */
126126+ #[TestDox('Format non-exceptional ordinal numeral 12')]
127127+ public function testFormatNonExceptionalOrdinalNumeral12(): void
128128+ {
129129+ $this->assertSame(
130130+ 'Juan, you are the 12th customer we serve today. Thank you!',
131131+ format('Juan', 12)
132132+ );
133133+ }
134134+135135+ /** uuid: 60a3187c-9403-4835-97de-4f10ebfd63e2 */
136136+ #[TestDox('Format non-exceptional ordinal numeral 13')]
137137+ public function testFormatNonExceptionalOrdinalNumeral13(): void
138138+ {
139139+ $this->assertSame(
140140+ 'Patricia, you are the 13th customer we serve today. Thank you!',
141141+ format('Patricia', 13)
142142+ );
143143+ }
144144+145145+ /** uuid: 2bdcebc5-c029-4874-b6cc-e9bec80d603a */
146146+ #[TestDox('Format exceptional ordinal numeral 21')]
147147+ public function testFormatExceptionalOrdinalNumeral21(): void
148148+ {
149149+ $this->assertSame(
150150+ 'Washi, you are the 21st customer we serve today. Thank you!',
151151+ format('Washi', 21)
152152+ );
153153+ }
154154+155155+ /** uuid: 74ee2317-0295-49d2-baf0-d56bcefa14e3 */
156156+ #[TestDox('Format exceptional ordinal numeral 62')]
157157+ public function testFormatExceptionalOrdinalNumeral62(): void
158158+ {
159159+ $this->assertSame(
160160+ 'Nayra, you are the 62nd customer we serve today. Thank you!',
161161+ format('Nayra', 62)
162162+ );
163163+ }
164164+165165+ /** uuid: b37c332d-7f68-40e3-8503-e43cbd67a0c4 */
166166+ #[TestDox('Format exceptional ordinal numeral 100')]
167167+ public function testFormatExceptionalOrdinalNumeral100(): void
168168+ {
169169+ $this->assertSame(
170170+ 'John, you are the 100th customer we serve today. Thank you!',
171171+ format('John', 100)
172172+ );
173173+ }
174174+175175+ /** uuid: 0375f250-ce92-4195-9555-00e28ccc4d99 */
176176+ #[TestDox('Format exceptional ordinal numeral 101')]
177177+ public function testFormatExceptionalOrdinalNumeral101(): void
178178+ {
179179+ $this->assertSame(
180180+ 'Zeinab, you are the 101st customer we serve today. Thank you!',
181181+ format('Zeinab', 101)
182182+ );
183183+ }
184184+185185+ /** uuid: 0d8a4974-9a8a-45a4-aca7-a9fb473c9836 */
186186+ #[TestDox('Format non-exceptional ordinal numeral 112')]
187187+ public function testFormatNonExceptionalOrdinalNumeral112(): void
188188+ {
189189+ $this->assertSame(
190190+ 'Knud, you are the 112th customer we serve today. Thank you!',
191191+ format('Knud', 112)
192192+ );
193193+ }
194194+195195+ /** uuid: 06b62efe-199e-4ce7-970d-4bf73945713f */
196196+ #[TestDox('Format exceptional ordinal numeral 123')]
197197+ public function testFormatExceptionalOrdinalNumeral123(): void
198198+ {
199199+ $this->assertSame(
200200+ 'Yma, you are the 123rd customer we serve today. Thank you!',
201201+ format('Yma', 123)
202202+ );
203203+ }
204204+}
+44
php/line-up/README.md
···11+# Line up
22+33+Welcome to Line up on Exercism's PHP Track.
44+If you need help running the tests or submitting your code, check out `HELP.md`.
55+66+## Introduction
77+88+Your friend Yaʻqūb works the counter at a deli in town, slicing, weighing, and wrapping orders for a line of hungry customers that gets longer every day.
99+Waiting customers are starting to lose track of who is next, so he wants numbered tickets they can use to track the order in which they arrive.
1010+1111+To make the customers feel special, he does not want the ticket to have only a number on it.
1212+They shall get a proper English sentence with their name and number on it.
1313+1414+## Instructions
1515+1616+Given a name and a number, your task is to produce a sentence using that name and that number as an [ordinal numeral][ordinal-numeral].
1717+Yaʻqūb expects to use numbers from 1 up to 999.
1818+1919+Rules:
2020+2121+- Numbers ending in 1 (unless ending in 11) → `"st"`
2222+- Numbers ending in 2 (unless ending in 12) → `"nd"`
2323+- Numbers ending in 3 (unless ending in 13) → `"rd"`
2424+- All other numbers → `"th"`
2525+2626+Examples:
2727+2828+- `"Mary", 1` → `"Mary, you are the 1st customer we serve today. Thank you!"`
2929+- `"John", 12` → `"John, you are the 12th customer we serve today. Thank you!"`
3030+- `"Dahir", 162` → `"Dahir, you are the 162nd customer we serve today. Thank you!"`
3131+3232+[ordinal-numeral]: https://en.wikipedia.org/wiki/Ordinal_numeral
3333+3434+## Source
3535+3636+### Created by
3737+3838+- @codedge
3939+- @neenjaw
4040+- @mk-mxp
4141+4242+### Based on
4343+4444+mk-mxp, based on previous work from Exercism contributors codedge and neenjaw - https://forum.exercism.org/t/new-exercise-ordinal-numbers/19147
+23
php/lucky-numbers/LuckyNumbers.php
···11+<?php
22+33+class LuckyNumbers
44+{
55+ public function sumUp(array $digitsOfNumber1, array $digitsOfNumber2): int
66+ {
77+ return (int) implode('', $digitsOfNumber1) + (int) implode('', $digitsOfNumber2);
88+ }
99+1010+ public function isPalindrome(int $number): bool
1111+ {
1212+ return $number == implode('', array_reverse(str_split((string) $number)));
1313+ }
1414+1515+ public function validate(string $input): string
1616+ {
1717+ return match (true) {
1818+ $input == '' => 'Required field',
1919+ (int) $input <= 0 => 'Must be a whole number larger than 0',
2020+ default => '',
2121+ };
2222+ }
2323+}
···11+# Lucky Numbers
22+33+Welcome to Lucky Numbers on Exercism's PHP Track.
44+If you need help running the tests or submitting your code, check out `HELP.md`.
55+If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
66+77+## Introduction
88+99+## Type Juggling
1010+1111+Type juggling may also be known as type coercion.
1212+Type juggling is when two values of different types are coerced to the same type to perform an operation.
1313+1414+```php
1515+$baskets = 5 // int
1616+$apples_per_basket = "3" // string
1717+$baskets * $apples_per_basket
1818+# => 15
1919+```
2020+2121+This can be done implicitly (see above example), or explicitly with `C`-style type casting:
2222+2323+```php
2424+$apples_per_basket = "3" // string
2525+$my_number = (int) $apples_per_basket // cast string to int
2626+# => 3 // int
2727+```
2828+2929+Allowed types for manual type casting are: `bool`, `int`, `float`, `string`, `array`, `object`.
3030+Most commonly, type casting is used to change an integer (`int`) to a float (`float`) or vice-versa.
3131+3232+## Instructions
3333+3434+Your friend Kojo is a big fan of numbers.
3535+He has a small website for playing around with numbers.
3636+Kojo is not that good at programming so he asked you for help.
3737+3838+You will build two helper methods for new number games on Kojos' website and a third one to validate some input the user can enter.
3939+4040+## 1. Calculate the sum for the numbers on the slot machine
4141+4242+One of the games on Kojos' website looks like a slot machine that shows two groups of wheels with digits on them.
4343+Each group of digits represents a number.
4444+For the game mechanics, Kojo needs to know the sum of those two numbers.
4545+4646+Write a method `sumUp` that accepts two arrays as parameters.
4747+Each array consists of one or more digits between 0 and 9.
4848+The function should interpret each array as a number and return the sum of those two numbers.
4949+5050+```php
5151+<?php
5252+$lucky_numbers = new LuckyNumbers()
5353+5454+$lucky_numbers->sumUp([1, 2, 3], [0, 7]);
5555+//=> 130
5656+5757+// [1, 2, 3] represents 123 and [0, 7] represents 7.
5858+// 123 + 7 = 130
5959+```
6060+6161+## 2. Determine if a number is a palindrome
6262+6363+Another game on the website is a little quiz called "Lucky Numbers".
6464+A user can enter a number and then sees whether the number belongs to some secret sequence or pattern.
6565+The sequence or pattern of the "lucky numbers" changes each month and each user only has a limited number of tries to guess it.
6666+6767+This months' lucky numbers should be numbers that are palindromes.
6868+Palindromic numbers remain the same when the digits are reversed.
6969+7070+Implement the new `isPalindrome` function that accepts an integer as a parameter.
7171+The function should return `true` if the number is a palindrome and `false` otherwise.
7272+The input number will always be a positive integer.
7373+7474+```php
7575+<?php
7676+$lucky_numbers = new LuckyNumbers()
7777+7878+$lucky_numbers->isPalindrome(1441);
7979+//=> true
8080+8181+$lucky_numbers->isPalindrome(123);
8282+//=> false
8383+```
8484+8585+## 3. Generate an error message for invalid user input
8686+8787+In various places on the website, there are input fields where the users can enter numbers and click a button to trigger some action.
8888+Kojo wants to improve the user experience of his site.
8989+He wants to show an error message in case the user clicks the button but the field contains an invalid input value.
9090+9191+Here is some more information on how the value of an input field is provided.
9292+9393+- If the user types something into a field, the associated value is always a string even if the user only typed in numbers.
9494+- If the user types something but deletes it again, the variable will be an empty string.
9595+- Before the user even started typing, the variable will be an empty string.
9696+9797+Write a function `validate` that accepts the user input as a parameter.
9898+If the user did not provide any input, `validate` should return `'Required field'`.
9999+If the input does not represent a positive non-zero whole number (according to the [PHP type casting rules][php-type-cast-int]), `'Must be a whole number larger than 0'` should be returned.
100100+In all other cases you can assume the input is valid, the return value should be an empty string then.
101101+102102+```php
103103+<?php
104104+$lucky_numbers = new LuckyNumbers()
105105+106106+$lucky_numbers->validate('123');
107107+// => ''
108108+109109+$lucky_numbers->validate('');
110110+// => 'Required field'
111111+112112+$lucky_numbers->validate('abc');
113113+// => 'Must be a whole number larger than 0'
114114+```
115115+116116+[php-type-cast-int]: https://www.php.net/manual/en/language.types.integer.php#language.types.integer.casting.from-string
117117+118118+## Source
119119+120120+### Created by
121121+122122+- @mk-mxp
+24
php/pizza-pi/PizzaPi.php
···11+<?php
22+33+class PizzaPi
44+{
55+ public function calculateDoughRequirement($pizzas, $persons): int
66+ {
77+ return $pizzas * (($persons * 20) + 200);
88+ }
99+1010+ public function calculateSauceRequirement(int $pizzas, int $sauceCanVolume): float|int
1111+ {
1212+ return $pizzas * 125 / $sauceCanVolume;
1313+ }
1414+1515+ public function calculateCheeseCubeCoverage(int $cheeseDimension, int|float $cheeseTickness, int $pizzaDiameter): int
1616+ {
1717+ return floor(($cheeseDimension**3) / ($cheeseTickness * pi() * $pizzaDiameter));
1818+ }
1919+2020+ public function calculateLeftOverSlices(int $pizzas, int $friends): float|int
2121+ {
2222+ return ($pizzas * 8) % $friends;
2323+ }
2424+}
+72
php/pizza-pi/PizzaPiTest.php
···11+<?php
22+33+use PHPUnit\Framework\TestCase;
44+use PHPUnit\Framework\Attributes\TestDox;
55+66+class PizzaPiTest extends TestCase
77+{
88+ public static function setUpBeforeClass(): void
99+ {
1010+ require_once 'PizzaPi.php';
1111+ }
1212+1313+ /**
1414+ * @task_id 1
1515+ */
1616+ #[TestDox('determine how much dough is required')]
1717+ public function testCalculateDoughRequirement()
1818+ {
1919+ $pizza_pi = new PizzaPi();
2020+ $actual = $pizza_pi->calculateDoughRequirement(5, 7);
2121+ $expected = 1700;
2222+ $this->assertEquals($expected, $actual);
2323+ }
2424+2525+ /**
2626+ * @task_id 2
2727+ */
2828+ #[TestDox('determine how many cans of sauce are required')]
2929+ public function testCalculateSauceRequirement()
3030+ {
3131+ $pizza_pi = new PizzaPi();
3232+ $actual = $pizza_pi->calculateSauceRequirement(8, 250);
3333+ $expected = 4;
3434+ $this->assertEquals($expected, $actual);
3535+ }
3636+3737+ /**
3838+ * @task_id 3
3939+ */
4040+ #[TestDox('determine how many pizzas a cube of cheese can cover')]
4141+ public function testCalculateCheeseCoverage()
4242+ {
4343+ $pizza_pi = new PizzaPi();
4444+ $actual = $pizza_pi->calculateCheeseCubeCoverage(25, 0.5, 30);
4545+ $expected = 331;
4646+ $this->assertEquals($expected, $actual);
4747+ }
4848+4949+ /**
5050+ * @task_id 4
5151+ */
5252+ #[TestDox('determine number of pieces remaining when evenly dividing')]
5353+ public function testCalculateLeftOverSlicesWithoutLeftOver()
5454+ {
5555+ $pizza_pi = new PizzaPi();
5656+ $actual = $pizza_pi->calculateLeftOverSlices(2, 4);
5757+ $expected = 0;
5858+ $this->assertEquals($expected, $actual);
5959+ }
6060+6161+ /**
6262+ * @task_id 4
6363+ */
6464+ #[TestDox('determine number of pieces remaining when not evenly dividing')]
6565+ public function testCalculateLeftOverSlicesWithLeftOver()
6666+ {
6767+ $pizza_pi = new PizzaPi();
6868+ $actual = $pizza_pi->calculateLeftOverSlices(4, 3);
6969+ $expected = 2;
7070+ $this->assertEquals($expected, $actual);
7171+ }
7272+}
+181
php/pizza-pi/README.md
···11+# Pizza Pi
22+33+Welcome to Pizza Pi on Exercism's PHP Track.
44+If you need help running the tests or submitting your code, check out `HELP.md`.
55+If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
66+77+## Introduction
88+99+## Integers
1010+1111+Integers are whole numbers that belong to the set `{..., -2, -1, 0, 1, 2, ...}`.
1212+The maximum and minimum values of an integer is determined by the system PHP runs in.
1313+Typically for most modern systems, the integer is a 64-bit value -- meaning it can represent `-9_223_372_036_854_775_808` to `9_223_372_036_854_775_807` inclusive.
1414+1515+```php
1616+<?php
1717+1818+$a = 1234;
1919+$a = 1_234_567;
2020+```
2121+2222+You can write integer literal values in other than base 10 using a base prefix:
2323+2424+```php
2525+<?php
2626+2727+$a = 0123; // octal number (equivalent to 83 decimal)
2828+$a = 0o123; // octal number (as of PHP 8.1.0)
2929+$a = 0x1A; // hexadecimal number (equivalent to 26 decimal)
3030+$a = 0b11111111; // binary number (equivalent to 255 decimal)
3131+```
3232+3333+> Taken from PHP's [documentation][syntax]
3434+3535+## Floating Point Numbers
3636+3737+Floating point numbers are used in PHP to represent a subset of real numbers.
3838+They start with one or more digits separated by a decimal separator.
3939+4040+```php
4141+<?php
4242+4343+$a = 1.234;
4444+$b = 1.2e3;
4545+$c = 7E-10;
4646+$d = 1_234.567; // as of PHP 7.4.0
4747+```
4848+4949+### Common pitfalls
5050+5151+Not all numbers and arithmetic operations can be performed with floating point numbers.
5252+Using floating point numbers to represent dollars and cents can lead to rounding errors.
5353+5454+```php
5555+<?php
5656+5757+$result = 0.1 + 0.2; // => 0.30000000000000004
5858+```
5959+6060+## Arithmetic Operators
6161+6262+PHP provides a number of operators for performing arithmetic operations. PHP follows the standard mathematical order of operations for its arithmetic operators. The operators that are provided by PHP are:
6363+6464+* Identity (+)
6565+* Negation (-)
6666+* Addition (+)
6767+* Subtraction (-)
6868+* Multiplication (*)
6969+* Division (/)
7070+* Modulo (%)
7171+* Exponentiation (**)
7272+7373+```php
7474+$moles = +'10';
7575+$aLotOfMoles = 6.022 * 10**23 * $moles;
7676+```
7777+7878+[syntax]: https://www.php.net/manual/en/language.types.integer.php#language.types.integer.syntax
7979+8080+## Instructions
8181+8282+Lilly loves cooking.
8383+She wants to throw a pizza party with her friends.
8484+To make the most of her ingredients, she needs to know how much of each ingredient she requires before starting.
8585+8686+To solve this, Lilly has drafted a program to automate some of the planning but needs your help finishing it.
8787+Will you help Lilly throw the proportionally perfect pizza party?
8888+8989+## 1. A Dough Ratio
9090+9191+Lilly is a fan of thin, crispy pizzas with a thinner crust.
9292+The dough needed for the middle is a minimum 200g, but every person it serves requires another 20g of dough.
9393+9494+`grams = pizzas * ((persons * 20) + 200)`
9595+9696+Lilly needs a function that:
9797+9898+- Takes the number of pizzas
9999+- The number of persons each pizza will serve
100100+- And returns the dough needed to the nearest gram.
101101+102102+For example, to make 4 pizzas that feed 8 people:
103103+104104+```php
105105+<?php
106106+107107+$pizza_pi = new PizzaPi();
108108+$pizza_pi->calculateDoughRequirement(4, 8);
109109+// => 1440
110110+```
111111+112112+## 2. A Splash of Sauce
113113+114114+Lilly is meticulous when applying her sauce, but the size of her pizzas can be inconsistent.
115115+From her experience, she knows that it takes 125mL of sauce per pizza.
116116+Lilly needs a function that calculates how many cans of sauce to buy.
117117+118118+`cans of sauce = pizzas * sauce per pizza / sauce can volume`
119119+120120+For example, given Lilly needs to make 8 pizzas, and each can is 250mL:
121121+122122+```php
123123+<?php
124124+125125+$pizza_pi = new PizzaPi();
126126+$pizza_pi->calculateSauceRequirement(8, 250);
127127+// => 4
128128+```
129129+130130+## 3. Some Cheese, Please
131131+132132+Cheese comes in perfect cubes and is sold by size.
133133+134134+The formula Lily uses is not the formula for the area of the pizza since she does _not_ want the cheese to cover the entire area.
135135+She decided to use the following formula to determine how many pizzas of some diameter (`diameter`) can be made from a cheese cube of some side-length (`cheese_dimension`):
136136+137137+`pizzas = (cheese_dimension³) / (thickness * PI * diameter)`
138138+139139+Create a function that:
140140+141141+- Takes a side-length dimension of a cheese cube
142142+- Takes the desired thickness of the cheese layer
143143+- Takes the diameter of the pizza
144144+- And uses Lilly's formula to return the number of pizzas that can be made while rounding down.
145145+146146+For example, given a 25x25x25cm cheese cube, 0.5cm thick cheese layer and pizzas 30cm in diameter:
147147+148148+```php
149149+<?php
150150+151151+$pizza_pi = new PizzaPi();
152152+$pizza_pi->calculateCheeseCubeCoverage(25, 0.5, 30);
153153+// => 331
154154+```
155155+156156+## 4. A Fair Share
157157+158158+Finally, Lilly wants her pizzas to divide into 8 slices each and distributed evenly among her friends.
159159+160160+Create a function that:
161161+162162+- Takes a number of pizzas and number of friends
163163+- and returns the number of slices that will be left over if each person takes an equal number of slices.
164164+165165+For example:
166166+167167+```php
168168+<?php
169169+170170+$pizza_pi = new PizzaPi();
171171+$pizza_pi->calculateLeftOverSlices(2, 4);
172172+// => 0
173173+$pizza_pi->calculateLeftOverSlices(4, 3);
174174+// => 2
175175+```
176176+177177+## Source
178178+179179+### Created by
180180+181181+- @neenjaw
+48
php/resistor-color-duo/README.md
···11+# Resistor Color Duo
22+33+Welcome to Resistor Color Duo on Exercism's PHP Track.
44+If you need help running the tests or submitting your code, check out `HELP.md`.
55+66+## Instructions
77+88+If you want to build something using a Raspberry Pi, you'll probably use _resistors_.
99+For this exercise, you need to know two things about them:
1010+1111+- Each resistor has a resistance value.
1212+- Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read.
1313+1414+To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values.
1515+Each band has a position and a numeric value.
1616+1717+The first 2 bands of a resistor have a simple encoding scheme: each color maps to a single number.
1818+For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15.
1919+2020+In this exercise you are going to create a helpful program so that you don't have to remember the values of the bands.
2121+The program will take color names as input and output a two digit number, even if the input is more than two colors!
2222+2323+The band colors are encoded as follows:
2424+2525+- black: 0
2626+- brown: 1
2727+- red: 2
2828+- orange: 3
2929+- yellow: 4
3030+- green: 5
3131+- blue: 6
3232+- violet: 7
3333+- grey: 8
3434+- white: 9
3535+3636+From the example above:
3737+brown-green should return 15, and
3838+brown-green-violet should return 15 too, ignoring the third color.
3939+4040+## Source
4141+4242+### Created by
4343+4444+- @MichaelBunker
4545+4646+### Based on
4747+4848+Maud de Vries, Erik Schierboom - https://github.com/exercism/problem-specifications/issues/1464
···11+<?php
22+33+/*
44+ * By adding type hints and enabling strict type checking, code can become
55+ * easier to read, self-documenting and reduce the number of potential bugs.
66+ * By default, type declarations are non-strict, which means they will attempt
77+ * to change the original type to match the type specified by the
88+ * type-declaration.
99+ *
1010+ * In other words, if you pass a string to a function requiring a float,
1111+ * it will attempt to convert the string value to a float.
1212+ *
1313+ * To enable strict mode, a single declare directive must be placed at the top
1414+ * of the file.
1515+ * This means that the strictness of typing is configured on a per-file basis.
1616+ * This directive not only affects the type declarations of parameters, but also
1717+ * a function's return type.
1818+ *
1919+ * For more info review the Concept on strict type checking in the PHP track
2020+ * <link>.
2121+ *
2222+ * To disable strict typing, comment out the directive below.
2323+ */
2424+2525+declare(strict_types=1);
2626+2727+use PHPUnit\Framework\TestCase;
2828+2929+class ResistorColorDuoTest extends TestCase
3030+{
3131+ private ResistorColorDuo $resistor;
3232+3333+ public static function setUpBeforeClass(): void
3434+ {
3535+ require_once 'ResistorColorDuo.php';
3636+ }
3737+3838+ public function setUp(): void
3939+ {
4040+ $this->resistor = new ResistorColorDuo();
4141+ }
4242+4343+ public function testBrownAndBlack(): void
4444+ {
4545+ $this->assertEquals(10, $this->resistor->getColorsValue(['brown', 'black']));
4646+ }
4747+4848+ public function testBlueAndGrey(): void
4949+ {
5050+ $this->assertEquals(68, $this->resistor->getColorsValue(['blue', 'grey']));
5151+ }
5252+5353+ public function testYellowAndViolet(): void
5454+ {
5555+ $this->assertEquals(47, $this->resistor->getColorsValue(['yellow', 'violet']));
5656+ }
5757+5858+ public function testWhiteAndRed(): void
5959+ {
6060+ $this->assertEquals(92, $this->resistor->getColorsValue(['white', 'red']));
6161+ }
6262+6363+ public function testOrangeAndOrange(): void
6464+ {
6565+ $this->assertEquals(33, $this->resistor->getColorsValue(['orange', 'orange']));
6666+ }
6767+6868+ public function testAdditionalColorsAreIgnored(): void
6969+ {
7070+ $this->assertEquals(51, $this->resistor->getColorsValue(['green', 'brown', 'orange']));
7171+ }
7272+7373+ public function testBlackAndBrownSingleDigit(): void
7474+ {
7575+ $this->assertEquals(1, $this->resistor->getColorsValue(['black', 'brown']));
7676+ }
7777+}
+58
php/resistor-color/README.md
···11+# Resistor Color
22+33+Welcome to Resistor Color on Exercism's PHP Track.
44+If you need help running the tests or submitting your code, check out `HELP.md`.
55+66+## Instructions
77+88+If you want to build something using a Raspberry Pi, you'll probably use _resistors_.
99+For this exercise, you need to know two things about them:
1010+1111+- Each resistor has a resistance value.
1212+- Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read.
1313+1414+To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values.
1515+Each band has a position and a numeric value.
1616+1717+The first 2 bands of a resistor have a simple encoding scheme: each color maps to a single number.
1818+1919+In this exercise you are going to create a helpful program so that you don't have to remember the values of the bands.
2020+2121+These colors are encoded as follows:
2222+2323+- black: 0
2424+- brown: 1
2525+- red: 2
2626+- orange: 3
2727+- yellow: 4
2828+- green: 5
2929+- blue: 6
3030+- violet: 7
3131+- grey: 8
3232+- white: 9
3333+3434+The goal of this exercise is to create a way:
3535+3636+- to look up the numerical value associated with a particular color band
3737+- to list the different band colors
3838+3939+Mnemonics map the colors to the numbers, that, when stored as an array, happen to map to their index in the array:
4040+Better Be Right Or Your Great Big Values Go Wrong.
4141+4242+More information on the color encoding of resistors can be found in the [Electronic color code Wikipedia article][e-color-code].
4343+4444+[e-color-code]: https://en.wikipedia.org/wiki/Electronic_color_code
4545+4646+## Source
4747+4848+### Created by
4949+5050+- @camilopayan
5151+5252+### Contributed to by
5353+5454+- @MichaelBunker
5555+5656+### Based on
5757+5858+Maud de Vries, Erik Schierboom - https://github.com/exercism/problem-specifications/issues/1458
···11+<?php
22+33+/*
44+ * By adding type hints and enabling strict type checking, code can become
55+ * easier to read, self-documenting and reduce the number of potential bugs.
66+ * By default, type declarations are non-strict, which means they will attempt
77+ * to change the original type to match the type specified by the
88+ * type-declaration.
99+ *
1010+ * In other words, if you pass a string to a function requiring a float,
1111+ * it will attempt to convert the string value to a float.
1212+ *
1313+ * To enable strict mode, a single declare directive must be placed at the top
1414+ * of the file.
1515+ * This means that the strictness of typing is configured on a per-file basis.
1616+ * This directive not only affects the type declarations of parameters, but also
1717+ * a function's return type.
1818+ *
1919+ * For more info review the Concept on strict type checking in the PHP track
2020+ * <link>.
2121+ *
2222+ * To disable strict typing, comment out the directive below.
2323+ */
2424+2525+declare(strict_types=1);
2626+2727+use PHPUnit\Framework\TestCase;
2828+2929+class ResistorColorTest extends TestCase
3030+{
3131+ public static function setUpBeforeClass(): void
3232+ {
3333+ require_once 'ResistorColor.php';
3434+ }
3535+3636+ public function testColors(): void
3737+ {
3838+ $this->assertEquals([
3939+ "black",
4040+ "brown",
4141+ "red",
4242+ "orange",
4343+ "yellow",
4444+ "green",
4545+ "blue",
4646+ "violet",
4747+ "grey",
4848+ "white"
4949+ ], getAllColors());
5050+ }
5151+5252+ public function testBlackColorCode(): void
5353+ {
5454+ $this->assertEquals(0, colorCode("black"));
5555+ }
5656+5757+ public function testOrangeColorCode(): void
5858+ {
5959+ $this->assertEquals(3, colorCode("orange"));
6060+ }
6161+6262+ public function testWhiteColorCode(): void
6363+ {
6464+ $this->assertEquals(9, colorCode("white"));
6565+ }
6666+}
+30
php/reverse-string/README.md
···11+# Reverse String
22+33+Welcome to Reverse String on Exercism's PHP Track.
44+If you need help running the tests or submitting your code, check out `HELP.md`.
55+66+## Introduction
77+88+Reversing strings (reading them from right to left, rather than from left to right) is a surprisingly common task in programming.
99+1010+For example, in bioinformatics, reversing the sequence of DNA or RNA strings is often important for various analyses, such as finding complementary strands or identifying palindromic sequences that have biological significance.
1111+1212+## Instructions
1313+1414+Your task is to reverse a given string.
1515+1616+Some examples:
1717+1818+- Turn `"stressed"` into `"desserts"`.
1919+- Turn `"strops"` into `"sports"`.
2020+- Turn `"racecar"` into `"racecar"`.
2121+2222+## Source
2323+2424+### Created by
2525+2626+- @MichaelBunker
2727+2828+### Based on
2929+3030+Introductory challenge to reverse an input string - https://www.freecodecamp.org/news/how-to-reverse-a-string-in-javascript-in-3-different-ways-75e4763c68cb
···11+<?php
22+33+use PHPUnit\Framework\TestCase;
44+use PHPUnit\Framework\Attributes\TestDox;
55+66+class HighSchoolSweetheartTest extends TestCase
77+{
88+ public static function setUpBeforeClass(): void
99+ {
1010+ require_once 'HighSchoolSweetheart.php';
1111+ }
1212+1313+ /**
1414+ * @task_id 1
1515+ */
1616+ #[TestDox('gets the first letter from a string')]
1717+ public function testFirstLetter()
1818+ {
1919+ $sweetheart = new HighSchoolSweetheart();
2020+ $this->assertEquals('J', $sweetheart->firstLetter('Jane'));
2121+ }
2222+2323+ /**
2424+ * @task_id 1
2525+ */
2626+ #[TestDox("getting the first letter doesn't change the case")]
2727+ public function testFirstLetterDoesNotChangeCase()
2828+ {
2929+ $sweetheart = new HighSchoolSweetheart();
3030+ $this->assertEquals('j', $sweetheart->firstLetter('jane'));
3131+ }
3232+3333+ /**
3434+ * @task_id 1
3535+ */
3636+ #[TestDox('getting the first letter removes whitespace from the name')]
3737+ public function testFirstLetterRemovesWhitespace()
3838+ {
3939+ $sweetheart = new HighSchoolSweetheart();
4040+ $this->assertEquals('J', $sweetheart->firstLetter(' Jane'));
4141+ }
4242+4343+ /**
4444+ * @task_id 2
4545+ */
4646+ #[TestDox('gets the first letter and appends a dot')]
4747+ public function testCreatesInitial()
4848+ {
4949+ $sweetheart = new HighSchoolSweetheart();
5050+ $this->assertEquals('B.', $sweetheart->initial('Betty'));
5151+ }
5252+5353+ /**
5454+ * @task_id 2
5555+ */
5656+ #[TestDox('creates an uppercase initial')]
5757+ public function testCreatesUppercaseInitial()
5858+ {
5959+ $sweetheart = new HighSchoolSweetheart();
6060+ $this->assertEquals('J.', $sweetheart->initial('jim'));
6161+ }
6262+6363+ /**
6464+ * @task_id 3
6565+ */
6666+ #[TestDox('creates a set of initials')]
6767+ public function testCreatesInitials()
6868+ {
6969+ $sweetheart = new HighSchoolSweetheart();
7070+ $this->assertEquals('L. M.', $sweetheart->initials('Linda Miller'));
7171+ }
7272+7373+ /**
7474+ * @task_id 4
7575+ */
7676+ #[TestDox('creates a set of initials, wrapped in a heart')]
7777+ public function testPair()
7878+ {
7979+ $sweetheart = new HighSchoolSweetheart();
8080+ $expected = <<<EXPECTED_HEART
8181+ ****** ******
8282+ ** ** ** **
8383+ ** ** ** **
8484+ ** * **
8585+ ** **
8686+ ** A. B. + C. D. **
8787+ ** **
8888+ ** **
8989+ ** **
9090+ ** **
9191+ ** **
9292+ ** **
9393+ ***
9494+ *
9595+ EXPECTED_HEART;
9696+9797+ $this->assertEquals(
9898+ $expected,
9999+ $sweetheart->pair('Avery Bryant', 'Charlie Dixon')
100100+ );
101101+ }
102102+}
+143
php/sweethearts/README.md
···11+# Highschool Sweethearts
22+33+Welcome to Highschool Sweethearts on Exercism's PHP Track.
44+If you need help running the tests or submitting your code, check out `HELP.md`.
55+If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
66+77+## Introduction
88+99+## Strings
1010+1111+Strings are a series of characters, surrounded by quotes.
1212+Both single and double quotes are supported.
1313+1414+```php
1515+<?php
1616+1717+$where = "Spain";
1818+$what = 'rain';
1919+```
2020+2121+### String variable expansion
2222+2323+In double quoted strings, if a variable is referenced while it is in scope, the value is inserted into the string.
2424+2525+```php
2626+<?php
2727+2828+$answer = 42;
2929+$expanded = "The answer to life, the universe, and everything is $answer";
3030+```
3131+3232+### Character Encoding Support
3333+3434+Historically, string functions in PHP only supported ASCII characters and did not support modern Unicode encodings.
3535+ASCII characters are each 1 byte, whereas Unicode characters may be 1-4 bytes in length.
3636+If needing to manipulate Unicode strings safely, refer to the [multibyte versions][multi-byte-fns] of the historic functions.
3737+3838+```php
3939+<?php
4040+4141+$byte_length = strlen('😃'); // => 4
4242+$string_length = mb_strlen('😃'); // => 1
4343+```
4444+4545+[multi-byte-fns]: https://www.php.net/manual/en/ref.mbstring.php
4646+4747+## Instructions
4848+4949+In this exercise, you are going to help high school sweethearts profess their love on social media by generating an ASCII heart with their initials:
5050+5151+```
5252+ ****** ******
5353+ ** ** ** **
5454+ ** ** ** **
5555+** * **
5656+** **
5757+** J. K. + M. B. **
5858+ ** **
5959+ ** **
6060+ ** **
6161+ ** **
6262+ ** **
6363+ ** **
6464+ ***
6565+ *
6666+```
6767+6868+## 1. Get the name's first letter
6969+7070+Implement the `HighSchoolSweetheart::firstLetter` function.
7171+It should take a name and return its first letter.
7272+It should clean up any unnecessary whitespace from the name.
7373+7474+```php
7575+<?php
7676+7777+$sweetheart = new HighSchoolSweetheart();
7878+$sweetheart->firstLetter("Jane");
7979+# => "J"
8080+```
8181+8282+## 2. Format the first letter as an initial
8383+8484+Implement the `HighSchoolSweetheart::initial` function.
8585+It should take a name and return its first letter, uppercase, followed by a dot.
8686+Make sure to reuse `HighSchoolSweetheart::first_letter` that you defined in the previous step.
8787+8888+```php
8989+<?php
9090+9191+$sweetheart = new HighSchoolSweetheart();
9292+$sweetheart->initial("jane");
9393+# => "J."
9494+```
9595+9696+## 3. Split the full name into the first name and the last name
9797+9898+Implement the `HighSchoolSweetheart::initials` function.
9999+It should take a full name, consisting of a first name and a last name separated by a space, and return the initials.
100100+Make sure to reuse `HighSchoolSweetheart::initial` that you defined in the previous step.
101101+102102+```php
103103+<?php
104104+105105+$sweetheart = new HighSchoolSweetheart();
106106+$sweetheart->initials("Jane Doe");
107107+# => "J. D."
108108+```
109109+110110+## 4. Put the initials inside of the heart
111111+112112+Implement the `HighSchoolSweetheart::pair` function.
113113+It should take two full names and return the initials inside an ASCII heart.
114114+Make sure to reuse `HighSchoolSweetheart::initials` that you defined in the previous step.
115115+116116+```php
117117+<?php
118118+119119+$sweetheart = new HighSchoolSweetheart();
120120+$sweetheart->pair("Blake Miller", "Riley Lewis")
121121+# => """
122122+# ****** ******
123123+# ** ** ** **
124124+# ** ** ** **
125125+# ** * **
126126+# ** **
127127+# ** B. M. + R. L. **
128128+# ** **
129129+# ** **
130130+# ** **
131131+# ** **
132132+# ** **
133133+# ** **
134134+# ***
135135+# *
136136+# """
137137+```
138138+139139+## Source
140140+141141+### Created by
142142+143143+- @neenjaw
+13
php/windowing-system/Position.php
···11+<?php
22+33+class Position
44+{
55+ public $y;
66+ public $x;
77+88+ public function __construct(int $y, int $x)
99+ {
1010+ $this->y = $y;
1111+ $this->x = $x;
1212+ }
1313+}
+30
php/windowing-system/ProgramWindow.php
···11+<?php
22+33+class ProgramWindow
44+{
55+ public $y;
66+ public $x;
77+88+ public $height;
99+ public $width;
1010+1111+ public function __construct()
1212+ {
1313+ $this->y = 0;
1414+ $this->x = 0;
1515+ $this->height = 600;
1616+ $this->width = 800;
1717+ }
1818+1919+ public function resize(Size $size): void
2020+ {
2121+ $this->height = $size->height;
2222+ $this->width = $size->width;
2323+ }
2424+2525+ public function move(Position $position): void
2626+ {
2727+ $this->y = $position->y;
2828+ $this->x = $position->x;
2929+ }
3030+}
+169
php/windowing-system/ProgramWindowTest.php
···11+<?php
22+33+use PHPUnit\Framework\TestCase;
44+use PHPUnit\Framework\Attributes\TestDox;
55+66+class ProgramWindowTest extends TestCase
77+{
88+ public static function setUpBeforeClass(): void
99+ {
1010+ require_once 'ProgramWindow.php';
1111+ require_once 'Size.php';
1212+ require_once 'Position.php';
1313+ }
1414+1515+ /**
1616+ * @task_id 1
1717+ */
1818+ #[TestDox('assert ProgramWindow has a $y property')]
1919+ public function testHasPropertyY()
2020+ {
2121+ $reflector = new ReflectionClass(ProgramWindow::class);
2222+ $this->assertHasProperty($reflector, 'y', [
2323+ 'has_type' => false,
2424+ 'has_default' => false
2525+ ]);
2626+ }
2727+2828+ /**
2929+ * @task_id 1
3030+ */
3131+ #[TestDox('assert ProgramWindow has a $x property')]
3232+ public function testHasPropertyX()
3333+ {
3434+ $reflector = new ReflectionClass(ProgramWindow::class);
3535+ $this->assertHasProperty($reflector, 'x', [
3636+ 'has_type' => false,
3737+ 'has_default' => false
3838+ ]);
3939+ }
4040+4141+ /**
4242+ * @task_id 1
4343+ */
4444+ #[TestDox('assert ProgramWindow has a $height property')]
4545+ public function testHasPropertyHeight()
4646+ {
4747+ $reflector = new ReflectionClass(ProgramWindow::class);
4848+ $this->assertHasProperty($reflector, 'height', [
4949+ 'has_type' => false,
5050+ 'has_default' => false
5151+ ]);
5252+ }
5353+5454+ /**
5555+ * @task_id 1
5656+ */
5757+ #[TestDox('assert ProgramWindow has a $width property')]
5858+ public function testHasPropertyWidth()
5959+ {
6060+ $reflector = new ReflectionClass(ProgramWindow::class);
6161+ $this->assertHasProperty($reflector, 'width', [
6262+ 'has_type' => false,
6363+ 'has_default' => false
6464+ ]);
6565+ }
6666+6767+ /**
6868+ * @task_id 2
6969+ */
7070+ #[TestDox('assert ProgramWindow has a constructor initial values')]
7171+ public function testHasConstructorSettingInitialValues()
7272+ {
7373+ $window = new ProgramWindow();
7474+ $this->assertEquals(0, $window->y);
7575+ $this->assertEquals(0, $window->x);
7676+ $this->assertEquals(600, $window->height);
7777+ $this->assertEquals(800, $window->width);
7878+ }
7979+8080+ /**
8181+ * @task_id 3
8282+ */
8383+ #[TestDox('assert Position class exists, with constructor, properties')]
8484+ public function testSizeHasConstructorSettingInitialValues()
8585+ {
8686+ $size = new Size(300, 700);
8787+ $this->assertEquals(300, $size->height);
8888+ $this->assertEquals(700, $size->width);
8989+ }
9090+9191+ /**
9292+ * @task_id 3
9393+ */
9494+ #[TestDox('assert ProgramWindow::resize function exists')]
9595+ public function testProgramWindowResize()
9696+ {
9797+ $window = new ProgramWindow();
9898+ $size = new Size(430, 2135);
9999+ $window->resize($size);
100100+ $this->assertEquals(430, $window->height);
101101+ $this->assertEquals(2135, $window->width);
102102+ }
103103+104104+ /**
105105+ * @task_id 4
106106+ */
107107+ #[TestDox('assert Position class exists, with constructor, properties')]
108108+ public function testPositionHasConstructorSettingInitialValues()
109109+ {
110110+ $position = new Position(30, 70);
111111+ $this->assertEquals(30, $position->y);
112112+ $this->assertEquals(70, $position->x);
113113+ }
114114+115115+ /**
116116+ * @task_id 4
117117+ */
118118+ #[TestDox('assert ProgramWindow::move function exists')]
119119+ public function testProgramWindowMove()
120120+ {
121121+ $window = new ProgramWindow();
122122+ $position = new Position(40, 235);
123123+ $window->move($position);
124124+ $this->assertEquals(40, $window->y);
125125+ $this->assertEquals(235, $window->x);
126126+ }
127127+128128+ private function assertHasProperty(
129129+ ReflectionClass $class,
130130+ string $name,
131131+ array $assertions = []
132132+ ) {
133133+ $assertions = array_merge([
134134+ 'has_type' => false,
135135+ 'has_default_value' => false
136136+ ], $assertions);
137137+138138+ try {
139139+ $property = $class->getProperty($name);
140140+ } catch (ReflectionException) {
141141+ $this->fail(
142142+ "Property '$name' missing from class '{$class->getName()}'"
143143+ );
144144+ };
145145+146146+ if ($assertions['has_type']) {
147147+ $this->assertTrue($property->hasType());
148148+ } else {
149149+ $this->assertFalse(
150150+ $property->hasType(),
151151+ "Property '$name' should not have a type declared"
152152+ );
153153+ }
154154+155155+ if ($assertions['has_default_value']) {
156156+ $this->assertTrue($property->hasDefaultValue());
157157+ } else {
158158+ if ($assertions['has_type'] === false) {
159159+ $this->assertTrue($property->hasDefaultValue());
160160+ $this->assertNull($property->getDefaultValue());
161161+ } else {
162162+ $this->assertFalse(
163163+ $property->hasDefaultValue(),
164164+ "Property '$name' should not have a default value declared"
165165+ );
166166+ }
167167+ }
168168+ }
169169+}
+170
php/windowing-system/README.md
···11+# Windowing System
22+33+Welcome to Windowing System on Exercism's PHP Track.
44+If you need help running the tests or submitting your code, check out `HELP.md`.
55+If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
66+77+## Introduction
88+99+## Classes
1010+1111+Classes are a unit of code organization in PHP.
1212+Code represents the behavior of an item or the behavior of a process can be grouped together as a class.
1313+Functions that are assigned to a class are called `methods` by convention.
1414+1515+```php
1616+<?php
1717+1818+class Door
1919+{
2020+ function lock()
2121+ {
2222+ // ...
2323+ }
2424+2525+ function unlock()
2626+ {
2727+ // ...
2828+ }
2929+}
3030+```
3131+3232+Classes are instantiated with the `new` keyword, this allocates memory for the class instance and calls the classes constructor method.
3333+Instantiated classes may be assigned to a variable.
3434+To invoke the methods of a class instance, we can use the `->` access operator.
3535+3636+```php
3737+<?php
3838+3939+$a_door = new Door();
4040+$a_door->lock();
4141+$a_door->unlock();
4242+```
4343+4444+An instantiated class may reference its own instance using the special `$this` variable.
4545+Classes may also have properties, these act as variables that are tied to the instance of the class object.
4646+These properties may be referenced internally or externally.
4747+4848+```php
4949+<?php
5050+5151+class Car
5252+{
5353+ public $color;
5454+5555+ function __construct($color)
5656+ {
5757+ $this->color = $color;
5858+ }
5959+6060+ function getColor()
6161+ {
6262+ return $this->color;
6363+ }
6464+}
6565+6666+$a_car = new Car("red");
6767+$a_car->color; // => "red" by accessing the property
6868+$a_car->getColor(); // => "red" by invoking the instance method
6969+```
7070+7171+## Instructions
7272+7373+In this exercise, you will be simulating a windowing based computer system.
7474+You will create some windows that can be moved and resized.
7575+The following image is representative of the values you will be working with below.
7676+7777+```text
7878+ ╔════════════════════════════════════════════════════════════╗
7979+ ║ ║
8080+ ║ position::$y,_ ║
8181+ ║ position::$x \ ║
8282+ ║ \<---- size::$width ----> ║
8383+ ║ ^ *──────────────────────┐ ║
8484+ ║ | │ title │ ║
8585+ ║ | ├──────────────────────┤ ║
8686+ ║ | │ │ ║
8787+ ║ size::$height │ │ ║
8888+ ║ | │ contents │ ║
8989+ ║ | │ │ ║
9090+ ║ | │ │ ║
9191+ ║ v └──────────────────────┘ ║
9292+ ║ ║
9393+ ║ ║
9494+ ╚════════════════════════════════════════════════════════════╝
9595+```
9696+9797+## 1. Define the properties of the Program Window
9898+9999+Using the provided class scaffold, provide the properties for the `y`, `x`, `height`, and `width` values.
100100+101101+```php
102102+<?php
103103+104104+$window = new ProgramWindow();
105105+$window->y; // => null
106106+$window->x; // => null
107107+$window->height; // => null
108108+$window->width; // => null
109109+```
110110+111111+## 2. Define the initial values for the program window
112112+113113+Define a constructor function for `ProgramWindow`.
114114+It should not take any arguments, but during the constructor execution set the default values for its properties.
115115+It should set the initial `y` and `x` values to `0`.
116116+It should set the initial `height` and `width` to a `600x800` screen size.
117117+118118+```php
119119+<?php
120120+121121+$window = new ProgramWindow();
122122+$window->y; // => 0
123123+$window->x; // => 0
124124+$window->height; // => 600
125125+$window->width; // => 800
126126+```
127127+128128+## 3. Define a function to resize the window
129129+130130+Define a new class in `Size.php` for a Size class.
131131+It should take constructor arguments to set the `height` and `width` properties.
132132+Additionally `ProgramWindow` requires a resize function, which receives the `Size` object instance and updates its own properties.
133133+134134+```php
135135+<?php
136136+137137+$window = new ProgramWindow();
138138+$size = new Size(764, 1080);
139139+$window->resize($size)
140140+141141+$window->height;
142142+// => 764
143143+$window->width;
144144+// => 1080
145145+```
146146+147147+## 4. Define a function to move the window
148148+149149+Define a new class in `Position.php` for a Position class.
150150+It should take constructor arguments to set the `y` and `x` properties.
151151+Additionally `ProgramWindow` requires a move function, which receives the `Position` object instance and updates its own properties.
152152+153153+```php
154154+<?php
155155+156156+$window = new ProgramWindow();
157157+$position = new Position(80, 313);
158158+$window->move($position)
159159+160160+$window->y;
161161+// => 80
162162+$window->x;
163163+// => 313
164164+```
165165+166166+## Source
167167+168168+### Created by
169169+170170+- @neenjaw
+13
php/windowing-system/Size.php
···11+<?php
22+33+class Size
44+{
55+ public int $height;
66+ public int $width;
77+88+ public function __construct(int $height, int $width)
99+ {
1010+ $this->height = $height;
1111+ $this->width = $width;
1212+ }
1313+}