Účelom tohoto projektu je si vyskúšať niektoré programovacie koncepty. Projekt používa minimalistický PHP framework mezzio (bývalý Zend Expressive), ktorý je PSR-15 kompatibilný, t.j na spracovanie requestov využíva systém middleware.
Názov Matrice (La Matrice) je z taliančiny a znamená matica. Zvolil som ho pretože aj názov frameworku je z taliančiny (Mezzio). V kóde ale používam anglické Skillmatrix.
Skill = zručnosť, schopnosť, vedomosť.
V projekte riešim vytváranie tzv. matice skillov. Matica skillov je tabuľka kde je každému človeku priradené hodnotenie (skóre) k určitej zručnosti.
Príklad matice skillov:
+--------+----------------------+-------------------+-----------------------+
| | Programming Language | Database Concepts | Debugging & Profiling |
+---------------------------------------------------------------------------+
| Tomas | 1 | 1 | 2 |
+---------------------------------------------------------------------------+
| Peter | 4 | 2 | 4 |
+---------------------------------------------------------------------------+
| Michal | 5 | 3 | 3 |
+--------+----------------------+-------------------+-----------------------+
Riešená je iba backendová časť vo forme REST API.
Projekt používa repository pattern (interface SkillmatrixRepository), konkrétne sú dáta ukladané do MySQL databáze cez Doctrine (DoctrineSkillmatrixRepository implements SkillmatrixRepository).
Dáta v databáze sú pre zjednodušenie uložené vo "flat" podobe v jednej tabuľke skillmatrix. Entity Person, Skill, Rating nemajú vlastné DB tabuľky ale sú uložené ako JSON v tabuľke skillmatrix.
Všetky ID sú typu UUID:
- ID entity dá sa generovať ešte pred vložením do DB
- UUID je jedinečné pre všetky entity naprieč tabuľami, serverami
- je bezpečné použiť ID v URL, nejde uhádnuť ďalší záznam v DB ako u auto-incremental primary key
TODO: Teraz je v MySQL skillmatrix ID stĺpec typu CHAR(36). To je neefektívne z pohľadu indexu, lepšie je BINARY(16), MySQL 8 má funkcie na ľahšie použitie UUID. U ostatných entít to je jedno pretože nemajú vlastnú tabuľku. Pre entitu Skill je prirodzenejšie ID typu natural (Programming Language => programming_language), nie UUID.
ID entity generuje Repository metódou nextIdentity() pretože ID entity by som mal vedieť vygenerovať ešte pred uložením entity do repository (entita by mala byť validná hneď po vytvorení, nie až po uložení). Keď generujem ID entity v repository môžem tak implementovať nielen gnerovanie ID typu UUID ale aj sekvencie alebo auto-increment.
V projekte používam pre väčšiu prehladnosť na vykonanie akcii pattern Command Bus. Command je objekt s dátami, ktoré určujú čo chce user vykonať. Handler je kód ktorý vykonáva daný Command. Každý Command má presne jeden handler. Používam knižnicu thephpleague/tactician.
Na validovanie vstupných dát používam knižnicu beberlei/assert upravenú tak aby vyhadzovala výnimky vo formáte ProblemDetails.
Dokumentácia API vo formáte API Blueprint a vo formáte HTML.
Telo requestu (ak sa posiela) je typu JSON (application/json), response je typu JSON HAL (application/hal+json). Použitá je knižnica mezzio/mezzio-hal.
Chybové stavy sa vracajú podľa štandardu Problem Details (application/problem+json). Použitá je knižnica mezzio/mezzio-problem-details.
Na testovanie API pri vývoji je dobrý nástroj Postman.
POST /skillmatrix
Body:
{
"persons": [
{
"id": "1df4a735-88b3-4590-a1fe-cde6e8c1bd73",
"name": "Michal"
},
{
"id": "71fb2218-c6e4-4293-a590-399fe7de70c4",
"name": "Vlado"
},
{
"id": "f1cdd62e-7df1-4f69-9acf-0ea80b98c52a",
"name": "Tibor"
}
],
"skills": [
{
"id": "0ae8b2a5-9b88-41a0-acd6-e929578de254",
"name": "Programming Language"
},
{
"id": "482362ba-fa9d-497a-b91a-f11547ca09e6",
"name": "Database Concepts"
},
{
"id": "a9b10288-6381-4f2a-9e9e-96f6bbc96bf2",
"name": "Debugging & Profiling"
},
{
"id": "e92d2416-a58e-4b59-b6dc-eae991142366",
"name": "Client-side Scripting"
}
]
}
Jedna osoba v matici môže mať priradené len jedno hodnotenie ku každému skillu. Ak hodnotenie už existuje vyhodím výnimku RatingAlreadyExists.
PUT /skillmatrix/{{matrixId}}/ratings
Body:
{
"personId": "1df4a735-88b3-4590-a1fe-cde6e8c1bd73",
"skillId": "0ae8b2a5-9b88-41a0-acd6-e929578de254",
"reviewer": {
"id": "2a264d46-2ca0-4bb2-8c79-b0b5aa7aec28",
"name": "Tomas"
},
"score": 3,
"note": "Test rating 1"
}
GET /skillmatrix/{{matrixId}}
Response:
{
"id": "50483c18-d5a4-4230-b253-4b1d962da756",
"persons": [
{
"id": "1df4a735-88b3-4590-a1fe-cde6e8c1bd73",
"name": "Michal"
},
{
"id": "71fb2218-c6e4-4293-a590-399fe7de70c4",
"name": "Vlado"
},
{
"id": "f1cdd62e-7df1-4f69-9acf-0ea80b98c52a",
"name": "Tibor"
}
],
"skills": [
{
"id": "0ae8b2a5-9b88-41a0-acd6-e929578de254",
"name": "Programming Language"
},
{
"id": "482362ba-fa9d-497a-b91a-f11547ca09e6",
"name": "Database Concepts"
},
{
"id": "a9b10288-6381-4f2a-9e9e-96f6bbc96bf2",
"name": "Debugging & Profiling"
},
{
"id": "e92d2416-a58e-4b59-b6dc-eae991142366",
"name": "Client-side Scripting"
}
],
"ratings": [
{
"personId": "1df4a735-88b3-4590-a1fe-cde6e8c1bd73",
"skillId": "0ae8b2a5-9b88-41a0-acd6-e929578de254",
"reviewer": {
"id": "2a264d46-2ca0-4bb2-8c79-b0b5aa7aec28",
"name": "Tomas"
},
"score": 3,
"note": "Test rating 1",
"created": "2020-03-17T20:20:31+00:00"
}
],
"_links": {
"self": {
"href": "http://127.0.0.1:10100/skillmatrix/50483c18-d5a4-4230-b253-4b1d962da756"
}
}
}
-
Naklonovať GIT repozitár:
git clone https://github.com/demijohn/matrice.git
-
Spustiť Docker:
docker-compose up -d
docker-compose exec app bash
-
Nainštalovať závislosti:
composer install
-
Vytvoriť .env súbor (prekopírovaním z distribučného .env.local):
cp .env.local .env
-
Spustiť databázové migrácie:
./vendor/bin/doctrine-migrations migrations:migrate
Databáza matrice sa vytvorí automaticky namountovaním súboru:
./docker/mysql-init/create_schemas.sql
ako entry pointu pre MySQL kontainer (viď docker-compose.yml
).
Používám PHPUnit. Spustenie testov:
./vendor/bin/phpunit
Integračne testy (CreateSkillmatrixActionTest, RatePersonActionTest) testujú response z API a kontroluje vrátený JSON. Na integračné testy som si vytvoril vlastnú triedu TestCase (extenduje TestCase PHPUnitu). V tejto triede sú metódy (get(), post(), patch()) na volanie API a metóda assertResponse() na testovanie response. V testoch sa API nevolá cez vrstvu HTTP ale priamo sa vytvoria objekty Request a posielajú sa do frameworku (handle() metóda z Mezzio\Application).
V testoch command handlerov testujem či je zavolána príslušná metóda z repository. Používam knižnicu Prophecy integrovanú do PHPUnit.
PHPUnit 9.4.0 by Sebastian Bergmann and contributors.
Runtime: PHP 7.4.7
Configuration: /var/www/phpunit.xml.dist
Create Skillmatrix Handler (MatriceTest\Handler\CreateSkillmatrixHandler)
✔ Create 165 ms
Rate Person Handler (MatriceTest\Handler\RatePersonHandler)
✔ Create 27 ms
Create Skillmatrix Action (MatriceTest\Integration\Action\CreateSkillmatrixAction)
✔ Create skillmatrix 615 ms
✔ Create skillmatrix with missing parameters 57 ms
✔ Create skillmatrix with invalid parameters 50 ms
Rate Person Action (MatriceTest\Integration\Action\RatePersonAction)
✔ Rate person 61 ms
✔ Rate for non existing skillmatrix 55 ms
✔ Rate already existing rating 53 ms
Doctrine Skillmatrix Repository (MatriceTest\Repository\DoctrineSkillmatrixRepository)
✔ Skillmatrix not found exception 38 ms
Time: 00:01.142, Memory: 8.00 MB
OK (9 tests, 40 assertions)
Na statickú analýzu kódu používam PHPStan na max. level. Spúšta sa:
./vendor/bin/phpstan analyse
TODO: Vytvoriť v composer.json skript na spúštanie PHPStan-u.
Na udržiavanie jednotného štýlu zdrojového kódu používam PHP_CodeSniffer. Spúšta sa:
./vendor/bin/phpcs -p -s
TODO: Vytvoriť v composer.json skript na spúštanie PHP_CodeSniffer-u.