PHPStan - PHP Static
Analyzer Tool
• Автор - Ondřej Mirtes
• Написан на PHP7
• Анализирует код PHP5/7
• На основе nikic/php-parser
!5
Slide 6
Slide 6 text
С чем мы работаем
• ~1 500 классов
• ~180 000 строк кода
• ~12 программистов PHP
• PSR
!6
Slide 7
Slide 7 text
Как мы следим за
качеством
• Жесткий код-стайл
• Ревью кода
• Придирчивый досмотр решения
• Приёмочные тесты
• Unit тесты
Slide 8
Slide 8 text
882 207
360
Потенциальные ошибки
Ложные срабатывания
Проблема с типами
1449 замечания
Slide 9
Slide 9 text
Поиск слабых мест
Попробуем вместе с PHPStan поискать
потенциальные ошибки и неточности в коде
Slide 10
Slide 10 text
class Product {}
class ProductRepository {
/** @return Product */
function find($productId) {
return $productId === 0 ? null : new Product();
}
}
Method ProductRepository::find() should return Product but
returns Product|null.
!10
Slide 11
Slide 11 text
class Product {}
class ProductRepository {
/** @return Product|null */
function find($productId) {
return $productId === 0 ? null : new Product();
}
}
!11
[OK] No errors
Slide 12
Slide 12 text
class Product {
private $name;
public function getName() { return $this->name; }
}
class ProductRepository {
/** @return Product|null */
public function find($productId) {
return $productId === 0 ? null : new Product();
}
}
$productRepository = new ProductRepository();
$product = $productRepository->find(0);
echo $product->getName();
Calling method getName() on possibly null value of type
Product|null.
!12
Slide 13
Slide 13 text
class Product {
private $name;
public function getName() { return $this->name; }
}
class ProductRepository {
/** @return Product|null */
public function find($productId) {
return $productId === 0 ? null : new Product();
}
}
$productRepository = new ProductRepository();
$product = $productRepository->find(0);
if ($product !== null) {
echo $product->getName();
}
!13
[OK] No errors
Slide 14
Slide 14 text
class Product {
private $name;
public function getName() { return $this->name; }
}
class ProductRepository {
/** @return Product|null */
public function find($productId) {
return $productId === 0 ? null : new Product();
}
}
$productRepository = new ProductRepository();
$product = $productRepository->find(0);
if ($product != null) {
echo $product->getName();
}
!14
[OK] No errors
Slide 15
Slide 15 text
class Product {
private $name;
public function getName() { return $this->name; }
}
class ProductRepository {
/** @return Product|null */
public function find($productId) {
return $productId === 0 ? null : new Product();
}
}
$productRepository = new ProductRepository();
$product = $productRepository->find(0);
if (!empty($product)) {
echo $product->getName();
}
!15
[OK] No errors
Slide 16
Slide 16 text
function method(array $properties = [])
{
if (isset($properties['s:referer'])) {
$properties['s:referer'] = 23;
}
if (isset($data['s:uri'])) {
$properties['s:uri'] = 43;
}
// ..
}
Variable $data in isset() is never defined.
!16
Slide 17
Slide 17 text
function method(array $properties = [])
{
if (isset($properties['s:referer'])) {
$properties['s:referer'] = 23;
}
if (isset($properties[’s:uri'])) {
$properties['s:uri'] = 43;
}
// ..
}
!17
[OK] No errors
Slide 18
Slide 18 text
class A {
private $field;
private $values;
private $any_not_null;
function __construct($field, $values, $any_not_null = false)
{
$this->field = $field;
$this->values = $values;
$this->any_not_null = false;
}
}
Constructor of class A has an unused parameter
$any_not_null.
!18
Slide 19
Slide 19 text
class A {
private $field;
private $values;
private $any_not_null;
function __construct($field, $values)
{
$this->field = $field;
$this->values = $values;
$this->any_not_null = false;
}
}
!19
[OK] No errors
Slide 20
Slide 20 text
class A {
private $field;
private $values;
private $any_not_null;
function __construct($field, $values, $any_not_null = false)
{
$this->field = $field;
$this->values = $values;
$this->any_not_null = $any_not_null;
}
}
!20
[OK] No errors
Slide 21
Slide 21 text
$array = [1,2,3,4,5,6];
$sortedArray = \sort($array);
foreach ($sortedArray as $item) {
echo $item;
}
Argument of an invalid type bool supplied for foreach, only
iterables are supported.
!21
Slide 22
Slide 22 text
$array = [1,2,3,4,5,6];
\sort($array);
foreach ($array as $item) {
echo $item;
}
[OK] No errors
!22
Slide 23
Slide 23 text
$a = new \stdoutClass();
Instantiated class stdoutClass not found.
!23
Slide 24
Slide 24 text
$a = new \stdClass();
[OK] No errors
!24
Slide 25
Slide 25 text
/**
* @param string $filename
* @return string
*/
function read($filename) {
return file_get_contents($filename);
}
echo read('filename');
Function read() should return string but returns false|
string
!25
Особенности
Как работает PHPStan при разных
обстоятельствах
Slide 30
Slide 30 text
/**
* @param string $filename
* @return string
*/
function read($filename) {
return (string)file_get_contents($filename);
}
/** @var string $filename */
$filename = new \stdClass();
read($filename);
[OK] No errors
!30
Slide 31
Slide 31 text
class A {
public function calc() { return 1; }
}
class AFactory {
/** @return object */
public function createA() {
return new A();
}
}
$factory = new AFactory();
$a = $factory->createA();
/** @var A $a */
$a->calc();
Call to an undefined method object::calc().
!31
Slide 32
Slide 32 text
class A {
public function calc() { return 1; }
}
class AFactory {
/** @return object */
public function createA() {
return new A();
}
}
$factory = new AFactory();
/** @var A $a */
$a = $factory->createA();
$a->calc();
[OK] No errors
!32
Slide 33
Slide 33 text
/**
* @param int $a
* @param int $b
* @return int
*/
function calc($a, $b) {
return (int)$a + (int)$b;
}
$r = calc(1,2);
•Casting to int something that's already int.
•Casting to int something that's already int.
!33
Slide 34
Slide 34 text
/**
* @param int|float|string|bool $a
* @param int|float|string|bool $b
* @return int
*/
function calc($a, $b) {
return (int)$a + (int)$b;
}
$r = calc(1,2);
[OK] No errors
!34
Slide 35
Slide 35 text
declare(strict_types=1);
/**
* @param int $a
* @param int $b
* @return int
*/
function calc(int $a, int $b): int {
return $a + $b;
}
$r = calc(1,2);
[OK] No errors
!35
Slide 36
Slide 36 text
/**
* @param int $a
* @param int $b
* @return int
*/
function calc($a, $b) {
return (int)$a + (int)$b;
}
$r = calc(1,2);
[OK] No errors
!36
parameters:
ignoreErrors:
- '#Casting to #'
Slide 37
Slide 37 text
Постепенное
внедрение
Slide 38
Slide 38 text
No content
Slide 39
Slide 39 text
!39
class A {
/** @var string */
private $a;
/** @var int */
private $b;
/**
* @param int $a
* @param int $b
*/
public function __construct($a, $b) {
$this->a = $a;
$this->b = (int)$b;
}
/** @param int $x */
public function setX($x) {
$this->x = $x ?: CONSTANT_VALUE;
}
/** @param int $b */
public function setB($b) {
$this->b = $b;
}
/** @return int */
public function getC() {
return $this->b === 2 ? null : $this->b;
}
}
$a = new A(2, 15);
$a->setX('23');
$a->setB($a ?: 2);
$c = $a->getC();
Пример с одной ошибкой из
каждого уровня
Он ужасен!
Slide 40
Slide 40 text
• 0 уровень
• 1-2 уровни
• 3-7 уровни
Slide 41
Slide 41 text
Сравнение
С другими продуктами
Slide 42
Slide 42 text
С чем будем сравнивать
• PHAN
• PHP Inspections (PHPStorm Plugin)
!42
Slide 43
Slide 43 text
PHPStan Phan
Workflow ДА ДА
Need PHP Extension НЕТ ДА
Can suppress specific
statement
НЕТ ДА
Can use in IDE НЕТ ДА (с демоном)
Multiple Cores НЕТ* ДА*
Plugins ДА ДА
Own Rule Set ДА ДА
Memory Usage 256MB* 2GB*
Time 13 секунд* 31 секунду*
Slide 44
Slide 44 text
Кто нашёл больше?
Slide 45
Slide 45 text
Итого
• Экономим время на ревью
• Качество проекта - выше
• Гибкость и расширяемость
• Скалярные типы + strict_types = easy
• Для средних и больших проектов