Top 10 PHP classic traps
Confoo, Montréal, Québec, Canada 2020
Agenda
hours 60 minutes
Top 10 classic PHP traps
Improve your code now
This may wait for monday…
321
WHO'S SPEAKING?
➤ Damien Seguy
➤ CTO at Exakat
➤ Static analysis tool
➤ Elephpant retirement home
A bug waiting in the code
A performance potential
A convenient tool
The way of the elephpant
Questions along the way
🐞
🛠
🐘
🚀
(When I want)
strpos(), the legendary
<?php
if (strpos($string, 'a'))  { }
if (strpos($string, 'a') == 0) { }
if ($x = strpos($string, 'a')) { }
🐞
The real face of strpos()
<?php
// Only for comparison with 0/false
if (strpos($string, 'a') === false) { }
// No zero, no confusion
if (strpos($string, 'a') == 2) { }
// strpos() is not the only one...
if (preg_match($regex, $string)) { }
🐘
strpos()-like
array_search()
collator_compare()
collator_get_sort_key()
current()
fgetc()
file_get_contents()
file_put_contents()
fread()
iconv_strpos()
iconv_strrpos()
imagecolorallocate()
imagecolorallocatealpha()
mb_strlen()
next()
pcntl_getpriority()
preg_match()
prev()
readdir()
stripos()
strpos()
strripos()
strrpos()
strtok()
curl_exec()
The lesser legend of strpos()
<?php
if (openssl_verify($data, 
$signature, 
$public_key)) {
    login($user);
}
?>
🐞
1 => success
0 => failure
-1 => error
=> true
=> false
=> true
random_int() throws
an exception
openssl_random_pseu
do_bytes() too, in PHP
7.4
openssl_verify()-like
pcntl_wait
ftp_size
pg_field_num
pg_set_client_encoding
ldap_compare
pcntl_waitpid
event_base_loop
openssl_pkcs7_verify
openssl_x509_checkpurpose
openssl_verify
posix_setsid
odbc_num_rows
odbc_num_fields
Define()
<?php 
define('A', true);
?>
🚀
From define() to const
<?php 
const A = true;
?>
🐘
define() is on the way out
<?php  
define('A', 3, true); 
define($x, $y); 
?>
🛠
Constant static expressions
<?php 
const A = true;
const B = 33 + 12 * (23 - 34);
const C = array(A, B, D::E);
const D = A ? B : C;
const E = [1] + C;
?>
🛠
Repeated print
<?php
  print 'a';
  print $b ;
  print 'c';
?>
🚀
Repeated print
<?php
  print 'a' . $b . 'c';
?>
🚀
Repeated print echo
<?php
  echo  'a' , $b , 'c';
?>
🐘
Repeated echo
<?php
  echo  'a',
$b ,
'c';
?>
echo doesn't "function"
<?php
  echo( 'a',
$b ,
'c',
);
?>
A reverse problem
<?php
$fp = fopen($file, 'w');
foreach($array as $row) {
  fputcsv($fp, $row);
}
fclose($fp);
?>
🚀
Dump it like Beckam
<?php
$fp = fopen('php://memory', 'w+');
foreach($array as $row) {
  fputcsv($fp, $row);
}
rewind($fp);
file_put_contents($file, 
stream_get_contents($fp));
?>
🚀
<?php
$b = 3; $c = 1;
$a1 = $b and $c;
$a2 = $b && $c;
?>
🐞
Logic, written in full
$a1? = true false 0 1 2 3
$a2? = true false 0 1 2 3
Logic, written in full
<?php
$b = 3; $c = 1;
$a1 = ($b and $c);
$a2 = $b && $c;
?>
🐘
Operator precedence
Order matters
<?php   
$x = new stdClass();
var_dump(!$x instanceof stdClass);
?>
Order matters
<?php    
$a = 1;
$b = 2;
echo '$a + $b = ' . $a + $b;
?>
🐞
PHP
8.0
Order matters
<?php   
echo -3 ** 2;
?>
🐞
<?php
$b = 3; $c = 1;
$a1 = $b and $c;
$a2 = $b & $c;
?>
🐞
A matter of string
<?php
$b = "A"; $c = "p";
$a = $b ^ $c;
?>
🐞
A matter of string A ^ m ,
A ^ n /
A ^ o .
A ^ p 1
A ^ q 0
A ^ r 3
A ^ s 2
A ^ t 5
A ^ u 4
A ^ v 7
A ^ w 6
A ^ x 9
A ^ y 8
Sneak master level 100
<?=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);
🐞
$_GET[_]($_GET[__]);
_GET
Far from reality
<?php
// DO NOT USE ANYMORE!
if (!is_real($a)) {
    $a = (real) $a;
}
?>
🐞
Whatever floats your boat
<?php
if (!is_float($a)) {
    $a = (float) $a;
}
?>
🐘
// Deprecated in in 7.4
Negative rounding
<?php    
echo round(1234.56, 2);
// 1234.56
echo round(1234.56, 0);
// 1235
echo round(1234.56, -2);
// 1200
?>
🐘
// In here since PHP/FI
(allegedly)
Substr(,,1) ?
<?php
$string = "abcde";
echo substr($string, $pos, 1);
?>
🚀
Substr(,,1) ?
<?php
$string = "abcde";
echo substr($string, $pos, 1);
echo $string[$pos];
?>
🐘
Substr(,,1) ?
<?php
$string = "abcde";
echo substr($string, -1, 1);
echo "$string[-1]abcde";
?>
🐘
PHP
7.1
鼠不尽的PHP<?php
$string = "ab⼈cde";
echo substr($string, $pos, 1);
echo $string[$pos];
echo mb_substr($string, $pos, 1);
// $pos = 1 => bbb
// $pos = 2 => ??⼈
// $pos = 3 => ??c
// String offset cast occurred
?>
🐘
substr() strikes again
<?php
$r = substr(strtolower($s), $o, $l);
?>
🚀
Substr() first!
<?php
$r = strtolower(substr($s, $o, $l));
$r = strtolower(dirname($s, 4));
?>
🐘
array_slice() first!
<?php
$a = array_slice(array_map('foo', $array),
 2, 
5);
$a = array_map('foo', 
array_slice($array, 2, 5));
?>
🚀
🐘
<?php
/* $array = [['name' => 'PHP', 
'version' => '7.4'],
             ['name' => 'exakat', 
'version' => '2.0.6'],...
*/
                    
$column = [];
foreach($array as $item) {
   $column[] = $item['name'];
}
?>
🐘
Developers just want one thing
Array
(
[0] => 'PHP'
[1] => 'exakat'
[2] => ...
)
Developers just want one thing
<?php
/* $array = [['name' => 'PHP', 
'version' => '7.4'],
             ['name' => 'exakat', 
'version' => '2.0.6'],...
*/       
$column = array_column($array, 'name');
?>
🐘
Array
(
[0] => 'PHP'
[1] => 'exakat'
[2] => ...
)
I want the index
<?php 
class x {
    public $a = 0;
    function __construct() {
$this->a = rand(0, 10);
}
}
$array  = array(new x, new x, new x);
array_column($array, 'a');
🐘
Array
(
[0] => 3
[1] => 7
[2] => 5
)
I want the property
<?php  
class x { 
    private $a = 0; 
    function __construct() { $this->a = rand(0, 10
    function __get($a) { return $this->a;}
} 
$array  = array(new x, new x, new x); 
array_column($array, 'a');
🐘
Array
(
[0] =>
[1] =>
[2] =>
)
I want the magic property
<?php  
class x { 
    private $a = 0; 
    function __construct() { $this->a = rand(0, 10
    function __get($a) { return $this->a;}
    function __isset($a) { return true;}
} 
$array  = array(new x, new x, new x); 
array_column($array, 'a');
🐘
Array
(
[0] => 3
[1] => 7
[2] => 5
)
I want the private property
<?php   
class x {  
    private $a = 0;  
    function __construct() { $this->a = rand(0, 
    function foo() { 
        $array  = array(new x, new x, new x);  
        print_r(array_column($array, 'a'));
    } 
}  
(new x)->foo();
🐘
Array
(
[0] => 3
[1] => 7
[2] => 5
)
Reindex it all
<?php
/* $array = [['name' => 'PHP', 
'version' => '7.4'],
             ['name' => 'exakat', 
'version' => '2.0.6'],...
*/       
$column = array_column($array, 
'name',
'version');
?>
🐘
Array
(
['PHP'] => '7.4'
['exakat'] => '2.0.6'
[...] => ...
)
Looping with count()
<?php
$array = foo();
for($i = 0; $i < count($n); $i++) {
    $array[$i] = strtoupper($array[$i]);
}
?>
🚀
Looping with count()
<?php
$array = foo();
foreach($array as &$a) {
$a = strtoupper($a);
}
?>
🐘
<?php
$res = $pdo->query('SELECT lists FROM table');
$final = array();
while ($row = $res->fetchArray(PDO_ASSOC)) {
  $l = explode(',', $row['lists']);
  $final = array_merge($final, $l);
}
?>
🚀
Looping with array_merge()
Looping with array_merge()
<?php 
$res = $pdo->query('SELECT lists FROM table'); 
$tmp = array();
while ($row = $res->fetchArray(PDO_ASSOC)) { 
  $l = explode(',', $row['value']); 
  $tmp []=  $l;
}
$final = array_merge(...$tmp); 
?>
🐘
<?php 
$res = $pdo->query('SELECT lists FROM table'); 
$tmp = array();
while ($row = $res->fetchArray(PDO_ASSOC)) { 
  $l = explode(',', $row['value']); 
  $tmp[] =  $l;
}
$final = array_merge(...$tmp); 
?>
🐘
Looping with array_merge()
<?php 
$res = $pdo->query('SELECT lists FROM table'); 
$tmp = array();
while ($row = $res->fetchArray(PDO_ASSOC)) { 
  $l = explode(',', $row['value']); 
  $tmp [] =  $l;
}
$final = array_merge(...$tmp); 
?>
🐘
Looping with array_merge()
Looping with concat
<?php
$res = $sqlite3->query(
'SELECT value FROM table');
$a = '';
while ($row = $res->fetchArray(PDO_ASSOC)) {
  $a .= $row['value'];
}
?>
🚀
<?php 
$res = $sqlite3->query(
        'SELECT value FROM table'); 
$a = array();
while ($row = $res->fetchArray(PDO_ASSOC)) { 
  $a []= $row['value']; 
} 
$final = implode('', $a);
?>
🐘
Looping with concat
Looping with addition
<?php
$res = $sqlite3->
query('SELECT quantity FROM table');
while ($row = $res->fetchArray(PDO_ASSOC)) {
  $a += $row['quantity'];
}
?>
🐘
No
array_sum
Missing subpattern
<?php
preg_match('/(a)(b)?/', 'abc', $r);
/*
Array
(
    [0] => ab
    [1] => a
    [2] => b
)
*/
🐞
Missing subpattern
<?php
preg_match('/(a)(b)?/', 'amc', $r);
/*
Array
(
    [0] => a
    [1] => a
)
*/
🐞
Missing subpattern
<?php
preg_match('/(a)(b)?(.?)/', 'amc', $r);
/*
Array
(
    [0] => am
    [1] => a
    [2] => 
    [3] => m
)
*/
🐘
Missing subpattern
<?php
preg_match('/(a)(b)?/', 'amc', $r,
PREG_UNMATCHED_AS_NULL);
/*
Array
(
    [0] => ad
    [1] => a
    [2] => 
)
*/
🐘
PHP
7.4+
The no-name™ brand
<?php  
preg_match('/(?<here>a)(b)?(.?)/', 'adc', $r); 
preg_match("/(?'here'a)(b)?(.?)/", 'adc', $r); 
/*
Array
(
    [0] => ad
[here] => a
    [1] => a
    [2] => 
    [3] => d
)
*/
🐘
The no-name™ brand<?php   
preg_match(
'/(?<here>a) #      named subpattern
(b)? #      optional b
(.?) #  because Damien told us
/x', 'abc', $r);  
print_r($r);
/* 
Array 
( 
    [0] => ad 
    [here] => a
    [1] => a 
    [2] =>  
    [3] => d 
) 
*/ 
🐘
Next month
<?php
echo date('F', 
strtotime('+1 month',
mktime(0,0,0,$i,31,2019)));
?>
// January 1rst => February 1rst
// October 31rst => December 1rst
// January 31rst => March, 2nd or 3rd
🐞
Next month
<?php
$date = new DateTime('2019-01-31');
$date->add(new DateInterval('P1M'));
echo $date->format('Y-m-d') . "n";
?>
🐞
// January 1rst => February 1rst
// October 31rst => December 1rst
// January 31rst => March, 2nd or 3rd
Next month
<?php
use CarbonCarbon;
$mutable = Carbon::createFromDate(2019, 1, 31);
$mutable->add(1, 'month');
print $mutable;
🐞
// January 1rst => February 1rst
// October 31rst => December 1rst
// January 31rst => March, 2nd or 3rd
Next month
<?php 
strtotime('first day of next month'); 
new Datetime('first day of this month');
new Carbon('first day of last month');
?>
🐘
When is tomorrow?
<?php
$tomorrow = time() + 86400;
?>
🐞
When is tomorrow?
<?php
$demain = new DateTime('tomorrow');
?>
🐘
How long does this last?
<?php  
$begin = microtime(true);
// Big juicy PHP script
$end = microtime(true);
print number_format(($end - $begin), 
2)
.'ms';
?>
🐞
How long does this last?
<?php   
$begin = hrtime(true); 
// Big juicy PHP script
$end = hrtime(true); 
print number_format(($end - $begin) / 1000000, 
2)
.'ms';
?>
🐘
The return of the reference
<?php 
$a = range(0, 3);
foreach($a as &$b) { }
foreach($a as $b) { }
print_r($a);
?>
Array
(
[0] => 0
[1] => 1
[2] => 2
[3] => 2
)
🐞
The return of the reference
<?php  
$a = range(0, 3); 
foreach($a as &$b) { } 
unset($b);
foreach($a as $b) { } 
print_r($a); 
?>
Array
(
[0] => 0
[1] => 1
[2] => 2
[3] => 3
)
🐘
list() with PHP 4
<?php  
list($a, $b) = array( 1, 2, 3);
?>
🛠
list() with PHP 5(-ish)
<?php  
[$a, $b] =  [ 1, 2, 3];
?>
🛠
🐘
list() with PHP 7
<?php  
['w' => $a, 'e' =>  $b] = 
['e' => 1,  
'w' => 'd']
];
?>
🛠
🐘
list() with PHP 7
<?php  
['w' => $a, 'e' => ['d'=> $b]] = 
['c' => 1,  
'w' =>['d' => 2, 
'f' => 3,]
,];
?>
🛠
🐘
list() with PHP 7
<?php  
['e' => $a, 'e' => ['d'=> $b]] = 
['c' => 1,  
'e' =>['d' => 2, 
'f' => 3,]
,];
?>
🛠
🐘
List() into the future!
<?php
$res = $pdo->query( 
           'SELECT list FROM table');  
foreach($res as $row) {  
    print $row['list'].PHP_EOL;
}  
?>
🛠
🐘
List() into the future!
<?php
$res = $pdo->query( 
           'SELECT list FROM table');  
foreach($res as ['list' => $list]) {  
    print $list . PHP_EOL;
}  
?>
🛠
🐘
Top 10
Dangling reference
For with count()
Next month
array_merge in loops
strpos() fail
Shorten first
unset($x->y)
Operator precedences
Missing subpattern
real is gone
Do you want to try those?
🛠
🐘
Exakat
PHP Static analysis
Modernize your code
https://www.exakat.io/
Merci @exakat
https://exakat.io
<?php
function g1() : Generator {
 for ($i = 0; $i < 4; ++$i ) { yield $i; }
}
function g2() : Generator {
 for ($i = 5; $i < 10; ++$i ) { yield $i; }
}
function aggregator() : Generator {
     yield from g1();
     yield from g2();
}
print_r(iterator_to_array());
Yield la clé 🐞
/*
Array
(
    [0] => 6
    [1] => 7
    [2] => 8
    [3] => 9
    [4] => 4  
    [5] => 5  
)
*/
Yield la clé
<?php
function g1() : Generator {
for ($i = 0; $i < 4; ++$i) { yield $i => $i; }
}
function g2() : Generator {
 for ($i = 5; $i < 10; ++$i) { yield $i => $i;}
}
function aggregator() : Generator {
     yield from g1();
     yield from g2();
}
print_r(iterator_to_array());
Yield la clé 🐘
/*
0 
1
2
3
4
5
6
7
8
9
*/
Yield la clé

Top 10 php classic traps confoo