4. Kilka przemyśleń
● każdy z nas ma swoje nawyki przy programowaniu
● pracując w zespole wypadałoby ujednolicić styl
● różne style koniec końców doprowadzą do konfliktów
● wszyscy znamy wojenki taby kontra spacje, prawda?
5. Kilka faktów
● PHP-FIG opracowuje PHP Standards Recommendations
● obecny standard stylu to PSR-12 określany jako
Extended Coding Style Guide
● dzięki niemu możemy ujednolicić pewne konwencje
6. Spójrzmy na ten bajzel:
<?php
namespace BrewmapCollections Builders;
use BrewmapModelsCountry;
use BrewmapCollections Countries as CountriesCollection ;
final class CountriesBuilder
{
static function buildFromJson (string $jsonFile) {
$countries = new CountriesCollection ();
$data = json_decode($jsonFile, true);
foreach($data['countries' ] as $countryData )
{
$country = null;
$countries->addCountry(new Country($countryData ["name"], $countryData ["symbol"]));
}
return $countries;
}
}
7. Wstępne code review:
<?php // gdzie jest declare(strict_types=1) i wolne linie?
namespace BrewmapCollections Builders;
use BrewmapModelsCountry; // dlaczego importy nie są alfabetycznie?
use BrewmapCollections Countries as CountriesCollection ;
final class CountriesBuilder // może Countries byłoby wystarczającą nazwą?
{ // gdzie modyfikator dostępu metody poniżej?
static function buildFromJson (string $jsonFile) { // co z tą klamrą? gdzie return type?
$countries = new CountriesCollection (); // czy to taby?
$data = json_decode($jsonFile, true); // przydałby się jakiś pusty wiersz
foreach($data['countries' ] as $countryData ) // apostrofy czy cudzysłowy?
{
$country = null; // co to za zmienna?
$countries->addCountry(new Country($countryData ["name"], $countryData ["symbol"]));
}
return $countries;
}
} // gdzie pusta linia na końcu pliku?
8. Lepiej?
<?php
declare(strict_types=1);
namespace BrewmapCollectionsBuilders;
use BrewmapCollectionsCountries as CountriesCollection;
use BrewmapModelsCountry;
final class Countries
{
public static function buildFromJson(string $jsonFile): CountriesCollection
{
$countries = new CountriesCollection();
$data = json_decode($jsonFile, true);
foreach ($data["countries"] as $countryData) {
$countries->addCountry(new Country($countryData["name"], $countryData["symbol"]));
}
return $countries;
}
}
16. Proste uruchomienie… a jednak nie?
./vendor/bin/ecs check
In AbstractCheckCommand.php line 123:
No checkers were found. Register them in your config in "services:" section, load them via "--config <file>.yml" or
"--set <set>" option.
check [--fix] [--clear-cache] [--no-progress-bar] [--no-error-table] [--output-format OUTPUT-FORMAT] [--]
[<source>...]
17. Jak to uruchomić?
Będziemy musieli utworzyć plik ecs.php w głównym
katalogu projektu. Można pobrać gotowy szablon z
repozytorium projektu.
W poprzednich wersjach był to YAML, dlatego warto
popatrzeć czy mamy ECS-a nowszego niż 6.0.0.
18. ecs.php z oficjalnego źródła
<?php
declare(strict_types=1);
use SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator;
use SymplifyEasyCodingStandardValueObjectOption;
use SymplifyEasyCodingStandardValueObjectSetSetList;
return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PATHS, [__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php']);
$parameters->set(
Option::SETS,
[
SetList::COMMON,
SetList::CLEAN_CODE,
SetList::DEAD_CODE,
SetList::PSR_12,
SetList::PHP_70,
SetList::PHP_71,
]
);
};
19. Dygresja konfiguracyjna
Swego czasu stworzyłem własny plik konfiguracyjny,
który kopiuję między różnymi projektami.
Przypuszczam, że przy większości projektów ecs.php
będzie z czasem mocno ewoluował. Nie bójcie się go
dostosować do swoich własnych potrzeb!
20. ecs.php
<?php
declare(strict_types=1);
use KrzysztofRewakPhpCsFixerDoubleQuoteFixerDoubleQuoteFixer;
use PhpCsFixerFixerCastNotationCastSpacesFixer;
use PhpCsFixerFixerClassNotationClassAttributesSeparationFixer;
use PhpCsFixerFixerOperatorNotOperatorWithSuccessorSpaceFixer;
use PhpCsFixerFixerPhpdocPhpdocLineSpanFixer;
use PhpCsFixerFixerStrictDeclareStrictTypesFixer;
use PhpCsFixerFixerStringNotationSingleQuoteFixer;
use SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator;
use SymplifyEasyCodingStandardValueObjectOption;
use SymplifyEasyCodingStandardValueObjectSetSetList;
$sets = [
SetList::CLEAN_CODE,
SetList::PSR_12,
SetList::PHP_71,
SetList::COMMON,
];
// dalsza część na drugiej stronie
22. Proste uruchomienie
./vendor/bin/ecs check
0/75 [░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
15/75 [▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░] 20%
30/75 [▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░] 40%
45/75 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░] 60%
60/75 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░] 80%
[OK] No errors found. Great job - your code is shiny in style!
75/75 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
23. Prosty przykład
Wróćmy zatem do przykładu z początku prezentacji.
Dodałem załączony kod do katalogu podpiętego pod ECS.
Po uruchomieniu programu otrzymałem całe mnóstwo
informacji na temat mojego stylu kodowania:
● tzw. diff
● listę zastosowanych checkerów
● dodatkowe informacje
24. Tzw. diff
--- Original
+++ New
@@ -1,16 +1,19 @@
<?php
+
+declare(strict_types=1);
+
namespace BrewmapCollectionsBuilders;
+use BrewmapCollectionsCountries as CountriesCollection;
use BrewmapModelsCountry;
-use BrewmapCollectionsCountries as CountriesCollection;
-final class CountriesBuilder
+final class Countries
{
- static function buildFromJson(string $jsonFile) {
+ public static function buildFromJson(string $jsonFile)
+ {
$countries = new CountriesCollection();
$data = json_decode($jsonFile, true);
- foreach($data['countries'] as $countryData)
- {
+ foreach ($data["countries"] as $countryData) {
$country = null;
$countries->addCountry(new Country($countryData["name"], $countryData["symbol"]));
}
27. Fix!
Znalezienie tych wszystkich błędów to wspaniała rzecz.
Jeszcze wspanialszą jest to, że ECS pod spodem korzysta
z wszystkich funkcjonalności pozostałych narzędzi.
Spróbujmy dodać flagę --fix do polecenia.
28. Magia!
ECS poprawił wszystkie znalezione błędy oprócz
nieszczęsnej zadeklarowanej, ale nigdzie nie używanej
zmiennej. Tę jedną zmianę trzeba zrobić ręcznie.
29. Dobra rada
Jeżeli dodajecie ECS do istniejącego projektu, szczególnie
sporych rozmiarów, warto uruchamiać --fix na osobnym
branchu, żeby przez przypadek nic nie popsuć, a
jednocześnie przy commitowaniu sprawdzić wszystkie
zmiany.
31. Kilka przemyśleń
● styl stylem, ale każdemu programiście zdarza się
zrobić nieprzemyślany błąd
● błąd może być “głupi”, na przykład if do którego nie da
się nigdy wejść
● ale czasami może to być coś bardziej
skomplikowanego i na pierwszy rzut oka
niewykrywalnego jak korzystanie z pól
niezadeklarowanych w konstruktorze
35. Proste uruchomienie… a jednak nie?
./vendor/bin/psalm
Could not locate a config XML file in path /application/. Have you run 'psalm --init' ?
./vendor/bin/psalm --init
Calculating best config level based on project files
Scanning files...
Analyzing files...
E░
Detected level 2 as a suitable initial default
Config file created successfully. Please re-run psalm.
36. Kilka przemyśleń
● Psalm znajduje mnóstwo rzeczy, ale niestety nie na
wszystkie mamy wpływ
● warto zastanowić się co naprawdę jest istotne i te
mniej ważne sprawy ignorować przez pole
issueHandlers w pliku konfiguracyjnym psalm.xml
37. Problem z laravelowymi Commandami?
./vendor/bin/psalm
Scanning files...
Analyzing files...
E░░░░░░░░░░I░░I░░░░░░░░░░░░░░░░░░░░░░░II░░░I░░░░░░░░░░░░░░░░ 60 / 78 (76%)
░░░░░░░░░░░░░░░░░░
ERROR: PropertyNotSetInConstructor - app/Console/Commands/ImportGoogleMap.php:16:7 - Property
BrewmapConsoleCommandsImportGoogleMap::$laravel is not defined in constructor of
BrewmapConsoleCommandsImportGoogleMap and in any methods called in the constructor (see https://psalm.dev/074)
class ImportGoogleMap extends Command
ERROR: PropertyNotSetInConstructor - app/Console/Commands/ImportGoogleMap.php:16:7 - Property
BrewmapConsoleCommandsImportGoogleMap::$name is not defined in constructor of
BrewmapConsoleCommandsImportGoogleMap and in any methods called in the constructor (see https://psalm.dev/074)
class ImportGoogleMap extends Command
ERROR: PropertyNotSetInConstructor - app/Console/Commands/ImportGoogleMap.php:16:7 - Property
BrewmapConsoleCommandsImportGoogleMap::$input is not defined in constructor of
BrewmapConsoleCommandsImportGoogleMap and in any methods called in the constructor (see https://psalm.dev/074)
class ImportGoogleMap extends Command
ERROR: PropertyNotSetInConstructor - app/Console/Commands/ImportGoogleMap.php:16:7 - Property
BrewmapConsoleCommandsImportGoogleMap::$output is not defined in constructor of
BrewmapConsoleCommandsImportGoogleMap and in any methods called in the constructor (see https://psalm.dev/074)
class ImportGoogleMap extends Command
44. Źle zwracane typy
use IlluminateHttpRedirectResponse ;
// (...)
public function redirectToFacebook (): RedirectResponse
{
return Socialite::driver("facebook")->redirect();
}
INFO: LessSpecificReturnStatement -
app/Http/Controllers/API/AuthenticationController.php:36:16
The type 'SymfonyComponentHttpFoundationRedirectResponse' is more general than the declared
return type 'IlluminateHttpRedirectResponse' for
BrewmapHttpControllersAPIAuthenticationController::redirectToFacebook (see
https://psalm.dev/129 )
return Socialite::driver("facebook")->redirect() ;
45. Źle zwracane typy
// GuzzleHttp/Client::_call() returns PromisePromiseInterface
class MockGuzzleClient extends Client
{
public function __call($method, $args): ResponseInterface
{
return new Response();
}
}
ERROR: ImplementedReturnTypeMismatch - app/Manager/MockGuzzleClient.php:20:16
The inherited return type 'GuzzleHttpPromisePromiseInterface' for GuzzleHttpClient::__call
is different to the implemented return type for CalclyCoreManagerMockGuzzleClient::__call
'PsrHttpMessageResponseInterface' (see https://psalm.dev/123 )
* @return ResponseInterface
46. Źle nazwane parametry metod
class Kernel extends ConsoleKernel
{
protected function commands(): void
{
$this->load(__DIR__ . "/Commands");
}
protected function renderException($output, Throwable $exception)
{
parent::renderException($output, $extension);
}
}
INFO: ParamNameMismatch - app/Console/Kernel.php:17:59
Argument 2 of BrewmapConsoleKernel::renderException has wrong name $exception, expecting $e
as defined by IlluminateFoundationConsoleKernel::renderException (see
https://psalm.dev/230 )
protected function renderException($output, Throwable $exception)
51. A może Github Workflow?
name: Behaviour, code and style test
on:
push:
branches: [ "develop" ]
pull_request :
branches: [ "develop" ]
jobs:
build:
# (...)
- name: Run test suite
run: docker-compose run php composer behat
- name: Run code style checker
run: docker-compose run php composer ecs
- name: Run code static analysis
run: docker-compose run php composer psalm