Automatyzacja utrzymania
jakości w środowisku PHP
Krzysztof Rewak
CTO w Blumilk
krzysztof.rewak@blumilk.pl
ECS
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?
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
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;
}
}
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?
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;
}
}
Jak to poprawić?
● ręcznie (och)
Jak to poprawić?
● ręcznie (och)
● korzystając z presetów w IDE i ulubionej kombinacji
klawiszowej: Ctrl+Alt+L
Jak to poprawić?
● ręcznie (och)
● korzystając z presetów w IDE i ulubionej kombinacji
klawiszowej: Ctrl+Alt+L
● zautomatyzować to!
Co nam daje Github?
Co nam daje Github?
Prosta instalacja
composer require symplify/easy-coding-standard--dev
Prosta instalacja
composer require symplify/easy-coding-standard --dev
Using version ^8.3 for symplify/easy-coding-standard
Running composer update symplify/easy-coding-standard
Loading composer repositories with package information
Updating dependencies
Lock file operations: 61 installs, 0 updates, 0 removals
- Locking composer/package-versions-deprecated (1.11.99)
- Locking composer/semver (3.2.2)
- Locking composer/xdebug-handler (1.4.4)
- Locking dealerdirect/phpcodesniffer-composer-installer (v0.7.0)
- Locking doctrine/annotations (1.11.1)
- Locking doctrine/lexer (1.2.1)
- Locking friendsofphp/php-cs-fixer (v2.16.7)
- Locking jean85/pretty-package-versions (1.5.1)
- Locking nette/finder (v2.5.2)
- Locking nette/robot-loader (v3.3.1)
- Locking nette/utils (v3.1.3)
- Locking nikic/php-parser (v4.10.2)
- Locking php-cs-fixer/diff (v1.3.1)
- Locking phpstan/phpdoc-parser (0.4.9)
- Locking phpstan/phpstan (0.12.52)
- Locking psr/cache (1.0.1)
- Locking psr/container (1.0.0)
- Locking psr/event-dispatcher (1.0.0)
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>...]
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.
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,
]
);
};
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!
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
ecs.php
$skipped = [
SingleQuoteFixer::class => null,
ClassAttributesSeparationFixer::class => null,
NotOperatorWithSuccessorSpaceFixer::class => null,
];
$rules = [
DeclareStrictTypesFixer::class => null,
CastSpacesFixer::class => ["space" => "none"],
DoubleQuoteFixer::class => null,
];
return static function (ContainerConfigurator $containerConfigurator) use ($sets, $skipped, $rules): void {
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::SETS, $sets);
$parameters->set(Option::SKIP, $skipped);
$parameters->set(Option::PATHS, ["app", "config", "database", "resources/lang", "routes"]);
$services = $containerConfigurator->services();
foreach ($rules as $rule => $configuration) {
$service = $services->set($rule);
if ($configuration) {
$service->call("configure", [$configuration]);
}
}
};
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%
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
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"]));
}
Zastosowane checkery
55/55 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
Applied checkers:
* KrzysztofRewakPhpCsFixerDoubleQuoteFixerDoubleQuoteFixer
* PhpCsFixerFixerBasicBracesFixer
* PhpCsFixerFixerBasicPsr4Fixer
* PhpCsFixerFixerClassNotationVisibilityRequiredFixer
* PhpCsFixerFixerImportOrderedImportsFixer
* PhpCsFixerFixerNamespaceNotationSingleBlankLineBeforeNamespaceFixer
* PhpCsFixerFixerPhpTagBlankLineAfterOpeningTagFixer
* PhpCsFixerFixerStrictDeclareStrictTypesFixer
Inne komunikaty
---------------------------------------------------------------------------------------------------------------------
backend/Collections/Builders/Countries.php:14
----------------------------------------------------------------------------------------------------------------------
Unused variable $country.
Reported by: "SlevomatCodingStandardSniffsVariablesUnusedVariableSniff.UnusedVariable"
----------------------------------------------------------------------------------------------------------------------
[ERROR] Found 1 error that needs to be fixed manually.
[WARNING] Good news is that 1 error is fixable! Just add "--fix" to console command and rerun to apply.
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.
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.
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.
PSALM
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
Co nam daje Github?
Prosta instalacja
composer require vimeo/psalm --dev
Prosta instalacja
composer require vimeo/psalm --dev
Using version ^4.1 for vimeo/psalm
./composer.json has been updated
Running composer update vimeo/psalm
Loading composer repositories with package information
Updating dependencies
Lock file operations: 13 installs, 0 updates, 0 removals
- Locking amphp/amp (v2.5.0)
- Locking amphp/byte-stream (v1.8.0)
- Locking dnoegel/php-xdg-base-dir (v0.1.1)
- Locking felixfbecker/advanced-json-rpc (v3.1.1)
- Locking felixfbecker/language-server-protocol (v1.5.0)
- Locking netresearch/jsonmapper (v2.1.0)
- Locking openlss/lib-array2xml (1.0.0)
- Locking phpdocumentor/reflection-common (2.2.0)
- Locking phpdocumentor/reflection-docblock (5.2.2)
- Locking phpdocumentor/type-resolver (1.4.0)
- Locking vimeo/psalm (4.1.0)
- Locking webmozart/assert (1.9.1)
- Locking webmozart/path-util (2.3.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 13 installs, 0 updates, 0 removals
- Downloading webmozart/assert (1.9.1)
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.
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
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
Problem z laravelowymi Commandami?
<issueHandlers >
<PropertyNotSetInConstructor >
<errorLevel type="suppress" >
< directory name="app/Console/Commands" />
< directory name="app/Nova/Metrics" />
< directory name="app/Http/Requests/" />
< directory name="app/Nova/Tools" />
< directory name="database/factories" />
</errorLevel>
</PropertyNotSetInConstructor >
</issueHandlers >
Kilka przykładów
● no to siup, obejrzyjmy kilka przykładów:
Głupie ify
if (false) {
die();
}
INFO: TypeDoesNotContainType - app/Console/Commands/ImportGoogleMap.php:30:12
if (false) is impossible (see https://psalm.dev/056 )
if(false) {
Nieużywany kod
class Localize
{
protected array $locales = ["en", "pl"];
protected Application $application;
protected string $legacy = "";
public function __construct(Application $application)
{
$this->application = $application;
}
ERROR: PossiblyUnusedProperty - app/Http/Middleware/Localize.php:16:22
Cannot find any references to property BrewmapHttpMiddlewareLocalize::$legacy (see
https://psalm.dev/149 )
protected string $legacy = "";
Głupotki tablicowe
protected function validateCheckSum(): void
{
$sum = 0;
for ($i = 0; $i < 10; $i++) {
$sum += self::CHECKSUM_WEIGHTS[$i] * $this->value[$i];
}
$checkSum = 10 - $sum % 10;
$checkNumber = $checkSum == 10 ? 0 : $checkSum;
ERROR: InvalidOperand - app/Providers/Validators/Pesel.php:72:50
Cannot perform a numeric operation with a non-numeric type string (see https://psalm.dev/058 )
$sum += self::CHECKSUM_WEIGHTS[$i] * $this->value[$i] ;
Niewłaściwe argumenty
public function render($request, Exception $exception): Response
{
$status = $exception->getCode() ?? Response::HTTP_INTERNAL_SERVER_ERROR;
// (...)
$response = response()->json($body, $status, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$this->logResponse($response);
return $response;
}
ERROR: InvalidScalarArgument - app/Exceptions/Handler.php:95:4 5
Argument 2 of IlluminateContractsRoutingResponseFactory::json expects int, int|string
provided (see https://psalm.dev/012 )
$response = response()->json($body, $status, [], JSON_UNESCAPED_UNICODE |
JSON_UNESCAPED_SLASHES);
Ź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() ;
Ź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
Ź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)
Wbudowana integracja z PHPStormem!
INTEGRACJA Z
PROJEKTEM
composer.json
{
"scripts": {
"post-autoload-dump" : [
"Illuminate FoundationComposerScripts::postAutoloadDump" ,
"@php artisan package:discover --ansi"
],
"psalm": "./vendor/bin/psalm" ,
"behat": "./vendor/bin/behat --format=progress" ,
"ecs": "./vendor/bin/ecs check" ,
"check": [
"composer psalm" ,
"composer behat" ,
"composer ecs"
]
}
}
Może git hooki?
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
Pytania?
krzysztof.rewak@blumilk.pl
@krzysztofrewak

More Related Content

PDF
Cykl życia zapytania HTTP (pod maską)
PDF
Laravelowe paczki do uwierzytelniania
PDF
Obalamy mity o wydajności frameworka Laravel cz. II
PDF
Laravel 8.0 - co nowego?
PPTX
Aplikacje internetowe real-time w oparciu o React/Redux
PDF
Wszystko o Laravel Livewire
PDF
PHP-PM. Hit czy kit?
PDF
Xdebug – debugowanie i profilowanie aplikacji PHP
Cykl życia zapytania HTTP (pod maską)
Laravelowe paczki do uwierzytelniania
Obalamy mity o wydajności frameworka Laravel cz. II
Laravel 8.0 - co nowego?
Aplikacje internetowe real-time w oparciu o React/Redux
Wszystko o Laravel Livewire
PHP-PM. Hit czy kit?
Xdebug – debugowanie i profilowanie aplikacji PHP

What's hot (20)

PDF
Laravel Octane - czy na pewno taki szybki?
PDF
Swoole w PHP. Czy to ma sens?
PDF
Droopler: instalacja z użyciem composer i przykład budowy prostej strony firm...
PDF
Migrate API w Drupalu [PL]
PDF
Websockety w PHP
PDF
Metaprogramowanie w JS
PPTX
PLNOG22 - Piotr Stolarek - Bezpieczeństwo użytkowania platform usługowych Tel...
PDF
Debugowanie skryptow php za pomoca xdebug
PDF
“Dziesięć serwerów poproszę!“, czyli co może Ci zaoferować definiowanie infra...
PDF
Deployment kodu z Capistrano
PDF
Laravel workshops 1
PDF
Laravel czy Lumen, oto jest pytanie
PDF
Laravel Dusk - prosty przepis na testy E2E
PDF
Uwierzytelnianie dwuetapowe (2FA) w Drupalu [PL]
PDF
CruiseControl.rb
PDF
Laravel Poznań Meetup #12 - "Laravel 6.0 - co nowego?"
PDF
Jak stworzyliśmy system kudosów w Laravelu i Slacku
PDF
Drupal jako modularny i rozszerzalny CMS [PL]
PDF
Apache http server - proste i zaawansowane przypadki użycia
PPTX
Jak zostać mobile deweloperem w 1 dzień
Laravel Octane - czy na pewno taki szybki?
Swoole w PHP. Czy to ma sens?
Droopler: instalacja z użyciem composer i przykład budowy prostej strony firm...
Migrate API w Drupalu [PL]
Websockety w PHP
Metaprogramowanie w JS
PLNOG22 - Piotr Stolarek - Bezpieczeństwo użytkowania platform usługowych Tel...
Debugowanie skryptow php za pomoca xdebug
“Dziesięć serwerów poproszę!“, czyli co może Ci zaoferować definiowanie infra...
Deployment kodu z Capistrano
Laravel workshops 1
Laravel czy Lumen, oto jest pytanie
Laravel Dusk - prosty przepis na testy E2E
Uwierzytelnianie dwuetapowe (2FA) w Drupalu [PL]
CruiseControl.rb
Laravel Poznań Meetup #12 - "Laravel 6.0 - co nowego?"
Jak stworzyliśmy system kudosów w Laravelu i Slacku
Drupal jako modularny i rozszerzalny CMS [PL]
Apache http server - proste i zaawansowane przypadki użycia
Jak zostać mobile deweloperem w 1 dzień
Ad

Similar to Automatyzacja utrzymania jakości w środowisku PHP (20)

PDF
WordUp Trójmiasto - Sage 9 w praktyce
PDF
JavaScript, Moduły
PDF
Ganymede - nowoczesne technologie w grach przeglądarkowych i mobilnych
PDF
Angular 4 pragmatycznie
PDF
Warsztaty: Podstawy PHP - część 2 - omówienie składni języka PHP (wersja 7)
PDF
Poznaj wp-config.php "Ukryte" możliwości.
PDF
Mongodb with Rails
PDF
Daj się wyręczyć - Joomla Day Polska 2014
PDF
Zastosowanie buildout przy wdrażaniu projektów opartych o framework Django
PDF
Michał Dec - Quality in Clouds
PDF
PLNOG 22 - Krzysztof Załęski - Praktyczne zastosowanie narzędzi NetDevOps
PPT
Podstawy PHP
PDF
PDF
Wprowadzenie do PHPUnit
PDF
Skalowanie PostgreSQL @ DBConf.PL 2014
PDF
xD bug - Jak debugować PHP-owe aplikacje (Xdebug)
PDF
Podstawy AngularJS
PDF
OSGi, deklaratywnie
PDF
Programowanie sterowników w Linuksie.
PDF
Webpack - Czym jest webpack i dlaczego chcesz go używać? - wersja krótka
WordUp Trójmiasto - Sage 9 w praktyce
JavaScript, Moduły
Ganymede - nowoczesne technologie w grach przeglądarkowych i mobilnych
Angular 4 pragmatycznie
Warsztaty: Podstawy PHP - część 2 - omówienie składni języka PHP (wersja 7)
Poznaj wp-config.php "Ukryte" możliwości.
Mongodb with Rails
Daj się wyręczyć - Joomla Day Polska 2014
Zastosowanie buildout przy wdrażaniu projektów opartych o framework Django
Michał Dec - Quality in Clouds
PLNOG 22 - Krzysztof Załęski - Praktyczne zastosowanie narzędzi NetDevOps
Podstawy PHP
Wprowadzenie do PHPUnit
Skalowanie PostgreSQL @ DBConf.PL 2014
xD bug - Jak debugować PHP-owe aplikacje (Xdebug)
Podstawy AngularJS
OSGi, deklaratywnie
Programowanie sterowników w Linuksie.
Webpack - Czym jest webpack i dlaczego chcesz go używać? - wersja krótka
Ad

More from Laravel Poland MeetUp (20)

PDF
WebRTC+Websockety - Jak stworzyłem aplikację do kamerek internetowych w Larav...
PDF
Kilka slajdów o castowaniu atrybutów w Eloquent
PDF
Licencje otwartego oprogramowania
PDF
Jak przyspieszyłem aplikację produkcyjną o ponad 40%
PDF
Jak przemycić Shape Up do Scruma?
PDF
Enumy w Laravelu - dlaczego warto stosować?
PDF
Przegląd najciekawszych wtyczek do Laravela
PDF
Walidacja w Laravelu
PDF
(prawie) Wszystko o Tinkerze
PDF
Laravel Jobs i PHP8
PDF
Laravel/PHP - zderzenie z PDFami
PDF
Action-based Laravel
PDF
Wstęp do Gitlab CI/CD w aplikacjach napisanych w Laravel
PDF
Laravel Collection - tablice na sterydach
PDF
AOP w Laravel
PDF
Speed up web API with Laravel and Swoole using Docker
PDF
Laravel 6.0 - co nowego?
PDF
Przetwarzanie Asynchroniczne i Promises w Laravel
PDF
KPI w projektach IT
PDF
Mikrousługi w allegro
WebRTC+Websockety - Jak stworzyłem aplikację do kamerek internetowych w Larav...
Kilka slajdów o castowaniu atrybutów w Eloquent
Licencje otwartego oprogramowania
Jak przyspieszyłem aplikację produkcyjną o ponad 40%
Jak przemycić Shape Up do Scruma?
Enumy w Laravelu - dlaczego warto stosować?
Przegląd najciekawszych wtyczek do Laravela
Walidacja w Laravelu
(prawie) Wszystko o Tinkerze
Laravel Jobs i PHP8
Laravel/PHP - zderzenie z PDFami
Action-based Laravel
Wstęp do Gitlab CI/CD w aplikacjach napisanych w Laravel
Laravel Collection - tablice na sterydach
AOP w Laravel
Speed up web API with Laravel and Swoole using Docker
Laravel 6.0 - co nowego?
Przetwarzanie Asynchroniczne i Promises w Laravel
KPI w projektach IT
Mikrousługi w allegro

Automatyzacja utrzymania jakości w środowisku PHP

  • 3. ECS
  • 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; } }
  • 9. Jak to poprawić? ● ręcznie (och)
  • 10. Jak to poprawić? ● ręcznie (och) ● korzystając z presetów w IDE i ulubionej kombinacji klawiszowej: Ctrl+Alt+L
  • 11. Jak to poprawić? ● ręcznie (och) ● korzystając z presetów w IDE i ulubionej kombinacji klawiszowej: Ctrl+Alt+L ● zautomatyzować to!
  • 12. Co nam daje Github?
  • 13. Co nam daje Github?
  • 14. Prosta instalacja composer require symplify/easy-coding-standard--dev
  • 15. Prosta instalacja composer require symplify/easy-coding-standard --dev Using version ^8.3 for symplify/easy-coding-standard Running composer update symplify/easy-coding-standard Loading composer repositories with package information Updating dependencies Lock file operations: 61 installs, 0 updates, 0 removals - Locking composer/package-versions-deprecated (1.11.99) - Locking composer/semver (3.2.2) - Locking composer/xdebug-handler (1.4.4) - Locking dealerdirect/phpcodesniffer-composer-installer (v0.7.0) - Locking doctrine/annotations (1.11.1) - Locking doctrine/lexer (1.2.1) - Locking friendsofphp/php-cs-fixer (v2.16.7) - Locking jean85/pretty-package-versions (1.5.1) - Locking nette/finder (v2.5.2) - Locking nette/robot-loader (v3.3.1) - Locking nette/utils (v3.1.3) - Locking nikic/php-parser (v4.10.2) - Locking php-cs-fixer/diff (v1.3.1) - Locking phpstan/phpdoc-parser (0.4.9) - Locking phpstan/phpstan (0.12.52) - Locking psr/cache (1.0.1) - Locking psr/container (1.0.0) - Locking psr/event-dispatcher (1.0.0)
  • 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
  • 21. ecs.php $skipped = [ SingleQuoteFixer::class => null, ClassAttributesSeparationFixer::class => null, NotOperatorWithSuccessorSpaceFixer::class => null, ]; $rules = [ DeclareStrictTypesFixer::class => null, CastSpacesFixer::class => ["space" => "none"], DoubleQuoteFixer::class => null, ]; return static function (ContainerConfigurator $containerConfigurator) use ($sets, $skipped, $rules): void { $parameters = $containerConfigurator->parameters(); $parameters->set(Option::SETS, $sets); $parameters->set(Option::SKIP, $skipped); $parameters->set(Option::PATHS, ["app", "config", "database", "resources/lang", "routes"]); $services = $containerConfigurator->services(); foreach ($rules as $rule => $configuration) { $service = $services->set($rule); if ($configuration) { $service->call("configure", [$configuration]); } } };
  • 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"])); }
  • 25. Zastosowane checkery 55/55 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% Applied checkers: * KrzysztofRewakPhpCsFixerDoubleQuoteFixerDoubleQuoteFixer * PhpCsFixerFixerBasicBracesFixer * PhpCsFixerFixerBasicPsr4Fixer * PhpCsFixerFixerClassNotationVisibilityRequiredFixer * PhpCsFixerFixerImportOrderedImportsFixer * PhpCsFixerFixerNamespaceNotationSingleBlankLineBeforeNamespaceFixer * PhpCsFixerFixerPhpTagBlankLineAfterOpeningTagFixer * PhpCsFixerFixerStrictDeclareStrictTypesFixer
  • 26. Inne komunikaty --------------------------------------------------------------------------------------------------------------------- backend/Collections/Builders/Countries.php:14 ---------------------------------------------------------------------------------------------------------------------- Unused variable $country. Reported by: "SlevomatCodingStandardSniffsVariablesUnusedVariableSniff.UnusedVariable" ---------------------------------------------------------------------------------------------------------------------- [ERROR] Found 1 error that needs to be fixed manually. [WARNING] Good news is that 1 error is fixable! Just add "--fix" to console command and rerun to apply.
  • 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.
  • 30. PSALM
  • 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
  • 32. Co nam daje Github?
  • 34. Prosta instalacja composer require vimeo/psalm --dev Using version ^4.1 for vimeo/psalm ./composer.json has been updated Running composer update vimeo/psalm Loading composer repositories with package information Updating dependencies Lock file operations: 13 installs, 0 updates, 0 removals - Locking amphp/amp (v2.5.0) - Locking amphp/byte-stream (v1.8.0) - Locking dnoegel/php-xdg-base-dir (v0.1.1) - Locking felixfbecker/advanced-json-rpc (v3.1.1) - Locking felixfbecker/language-server-protocol (v1.5.0) - Locking netresearch/jsonmapper (v2.1.0) - Locking openlss/lib-array2xml (1.0.0) - Locking phpdocumentor/reflection-common (2.2.0) - Locking phpdocumentor/reflection-docblock (5.2.2) - Locking phpdocumentor/type-resolver (1.4.0) - Locking vimeo/psalm (4.1.0) - Locking webmozart/assert (1.9.1) - Locking webmozart/path-util (2.3.0) Writing lock file Installing dependencies from lock file (including require-dev) Package operations: 13 installs, 0 updates, 0 removals - Downloading webmozart/assert (1.9.1)
  • 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
  • 38. Problem z laravelowymi Commandami? <issueHandlers > <PropertyNotSetInConstructor > <errorLevel type="suppress" > < directory name="app/Console/Commands" /> < directory name="app/Nova/Metrics" /> < directory name="app/Http/Requests/" /> < directory name="app/Nova/Tools" /> < directory name="database/factories" /> </errorLevel> </PropertyNotSetInConstructor > </issueHandlers >
  • 39. Kilka przykładów ● no to siup, obejrzyjmy kilka przykładów:
  • 40. Głupie ify if (false) { die(); } INFO: TypeDoesNotContainType - app/Console/Commands/ImportGoogleMap.php:30:12 if (false) is impossible (see https://psalm.dev/056 ) if(false) {
  • 41. Nieużywany kod class Localize { protected array $locales = ["en", "pl"]; protected Application $application; protected string $legacy = ""; public function __construct(Application $application) { $this->application = $application; } ERROR: PossiblyUnusedProperty - app/Http/Middleware/Localize.php:16:22 Cannot find any references to property BrewmapHttpMiddlewareLocalize::$legacy (see https://psalm.dev/149 ) protected string $legacy = "";
  • 42. Głupotki tablicowe protected function validateCheckSum(): void { $sum = 0; for ($i = 0; $i < 10; $i++) { $sum += self::CHECKSUM_WEIGHTS[$i] * $this->value[$i]; } $checkSum = 10 - $sum % 10; $checkNumber = $checkSum == 10 ? 0 : $checkSum; ERROR: InvalidOperand - app/Providers/Validators/Pesel.php:72:50 Cannot perform a numeric operation with a non-numeric type string (see https://psalm.dev/058 ) $sum += self::CHECKSUM_WEIGHTS[$i] * $this->value[$i] ;
  • 43. Niewłaściwe argumenty public function render($request, Exception $exception): Response { $status = $exception->getCode() ?? Response::HTTP_INTERNAL_SERVER_ERROR; // (...) $response = response()->json($body, $status, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $this->logResponse($response); return $response; } ERROR: InvalidScalarArgument - app/Exceptions/Handler.php:95:4 5 Argument 2 of IlluminateContractsRoutingResponseFactory::json expects int, int|string provided (see https://psalm.dev/012 ) $response = response()->json($body, $status, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
  • 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)
  • 47. Wbudowana integracja z PHPStormem!
  • 49. composer.json { "scripts": { "post-autoload-dump" : [ "Illuminate FoundationComposerScripts::postAutoloadDump" , "@php artisan package:discover --ansi" ], "psalm": "./vendor/bin/psalm" , "behat": "./vendor/bin/behat --format=progress" , "ecs": "./vendor/bin/ecs check" , "check": [ "composer psalm" , "composer behat" , "composer ecs" ] } }
  • 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