45
头图

Preface

When I checked the PHP document by chance, I found some interesting content. With the increase of reading, there are more and more interesting content or time pits, so I decided to record it and share it. Some excerpts of the content below User notes from some excellent blogs, PHP documents, or original documents.

Especially the original document, I found that many people don’t read it, and don’t pay attention to many things. (Yes, I do, too, so take this opportunity to learn together.)

I forgot the order of the parameters of the PHP function. Are they random?

PHP is a glue that brings together hundreds of external libraries, so sometimes this gets messy. However, a simple rule of thumb is as follows:

Array functionparameters are ordered as " needle, haystack" whereas String functionsare the opposite, so " haystack, needle".

Translation: The parameter order of the array related method is "needle, haystack", and the string related method is the opposite "haystack, needle",

Source: https://www.php.net/manual/zh/faq.using.php#faq.using.parameterorder

How should I preserve "salt"?

When using the password_hash() or crypt() function, the "salt" will be returned as part of the generated hash value. You can directly store the complete return value in the database, because this return value already contains enough information, you can directly use the password_verify() or crypt() function for password verification.

The following figure shows the structure of the return value of the crypt() or password_hash() function. As you can see, the algorithm information and the "salt" are already included in the return value, which will be used in subsequent password verification.

Source: https://www.php.net/manual/zh/faq.passwords.php#faq.password.storing-salts

Why isn’t the following code divided into two lines?

<pre>
<?php echo "This should be the first line."; ?>
<?php echo "This should show up after the new line above."; ?>
</pre>

In PHP, the end tag of a piece of code is either "?>" or "?>\n" (\n means line break). So in the above example, the output sentence will be displayed on the same line, because PHP ignores the newline after the end of code tag. This means that if you want to output a newline character, you need to add an extra newline after the end tag of each piece of PHP code.

Why does PHP do this? This is usually easier when formatting normal HTML. If a line break is output and you don't need this line break, you have to use a very long line to achieve this effect, or make the format of the source file of the generated HTML page difficult to read.

Source: https://www.php.net/manual/zh/faq.using.php#faq.using.newlines

The priority of string concatenation operators

If you run the following code, it will output a warning and result 3 , because the string concatenation operator . and the mathematical operators + , - the same priority, they will be executed from left to right. Result: will be forcibly converted into the array 0 . If you are running in a lower version of PHP, it will tell you that the middle is not a number. If you are running in 7.4, it will tell you that + and - in PHP 8 will be increased. If you use the EA plugin in PHPSTORM, it will remind you of this problem.

<php
$var = 3;

echo "Result: " . $var + 3;

If you don't want this, it is best to wrap it in parentheses, as shown below.

<?php
$var = 3;

echo "Result: " . ($var + 3);

Source: https://www.php.net/manual/zh/language.operators.string.php#41950

String concatenation operator and numbers

Run the following code, especially the third line. Please note that if . , then even a number will act as a string concatenation.

<?php

echo "thr"."ee";           //prints the string "three"
echo "twe" . "lve";        //prints the string "twelve"
echo 1 . 2;                //prints the string "12"
echo 1.2;                  //prints the number 1.2
echo 1+2;                  //prints the number 3

Source: https://www.php.net/manual/zh/language.operators.string.php#41950

Use http_build_query

NULL values will be ignored

<?php
$arr = array('test' => null, 'test2' => 1);

// test2=1
echo http_build_query($arr); 

Source: https://www.php.net/manual/zh/function.http-build-query.php#60523

True and False will be converted to numbers

<?php
$a = [teste1= true,teste2=false];
// teste1=1&teste2=0
echo http_build_query($a)

Source: https://www.php.net/manual/zh/function.http-build-query.php#122232

Empty array will not appear in the result

<?php

$post_data = array('name'=>'miller', 'address'=>array('address_lines'=>array()), 'age'=>23);
// name=miller&age=23
echo http_build_query($post_data);

Source: https://www.php.net/manual/zh/function.http-build-query.php#109466

Briefly describe the principle of OpCache

PHP executes this code will go through the following 4 steps (to be precise, it should be PHP's language engine Zend)

  • 1. Scanning (Lexing), convert PHP code into language fragments (Tokens)
  • 2. Parsing, convert Tokens into simple and meaningful expressions
  • 3. Compilation, compile the expression into Opocdes
  • 4. Execution, execute Opcodes sequentially, one at a time, so as to realize the function of PHP script.

Nowadays, some Caches such as APC can make PHP cache Opcodes, so that every time a request comes, there is no need to repeat the previous 3 steps, which can greatly improve the execution speed of PHP.

Source: https://www.laruence.com/2008/06/18/221.html

What does var_dump(1...9) output?

<?php

// 10.9
var_dump(1...9);

Output 10.9. At first glance, the output of this var_dump looks strange, isn't it? why?

Here to teach you, if you see a piece of PHP code and feel that the output is very strange, the first reaction is to see what the opcodes generated by this code are. Although this problem is actually a problem in the lexical analysis stage, it is better to analyze it with phpdbg (generally In order to prevent the influence of opcache, -n) will be passed:

phpdbg -n -p /tmp/1.php
function name: (null)
L1-35 {main}() /tmp/1.php - 0x7f56d1a63460 + 4 ops
L2 #0 INIT_FCALL<1> 96 "var_dump"
L2 #1 SEND_VAL "10.9" 1
L2 #2 DO_ICALL
L35 #3 RETURN<-1> 1

So it seems that before the opcode was generated, 1...9 became a constant 10.9. Considering that this is a literal, let's look at zend_language_scanner.l and find this line:

DNUM ({LNUM}?"."{LNUM})|({LNUM}"."{LNUM}?)

This is the format of floating-point numbers defined by lexical analysis.
1...9 will be sequentially accepted as: 1. (floating point number 1), then. (string concatenation symbol) and then .9 (floating point number 0.9)

So in the compilation stage, "1" will be generated directly. "0.9" -> the literal value of the string "10.9"

Source: https://www.laruence.com/2020/02/23/1990.html

HTTPOXY vulnerability

The core background here is that for a long time we are used to using an environment variable called "http_proxy" to set up our request proxy.

http_proxy=127.0.0.1:9999 wget http://www.laruence.com/

How to form?

In CGI (RFC 3875) mode, the Header in the request will be prefixed with HTTP_ and registered as an environment variable, so if you send a Proxy:xxxxxx in the Header, then PHP will register it as the HTTP_PROXY environment Variable, so getenv("HTTP_PROXY") becomes controllable. Then if all your similar requests will be proxied to the address the attacker wants, then the attacker can forge, monitor, and tamper with your Requested

How does it affect?

Therefore, if this vulnerability affects you, there are several core premises:

  • Your service will request external resources
  • Your service uses the HTTP_PROXY (uppercase) environment variable to proxy your request (maybe you write it yourself, or use some flawed library)
  • Your service runs in PHP's CGI mode (cgi, php-fpm)

How to deal with it?

Take Nginx as an example, add in the configuration:

fastcgi_param HTTP_PROXY "";

So it is recommended that even if you are not affected by this vulnerability, you should also add this configuration.
And if you are the author of a class library, or you have no way to modify the service configuration for any reason, then you need to add the judgment of sapi in the code, unless it is in the cli mode, you should never trust the http_proxy environment variable.

<?php
if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) {
  //只有CLI模式下, HTTP_PROXY环境变量才是可控的
}

Supplement: Starting from PHP5.5.38, getenv has added a second parameter, local_only = false, if this parameter is true, it will only be obtained from the system's local environment variable table, thereby fixing this problem, and the default PHP will intercept HTTP_PROXY: fix

HTTPOXY Vulnerability Description-The Corner of Wind and Snow
https://www.laruence.com/2016/07/19/3101.html

Operator precedence

&& and and in the assignment operation

Run the following code, the first $bool will print as false , which is expected, but the second $bool will print true . This is because = is higher than the and operator, so the second $bool will be executed ($bool = true) and false

<?php

$bool = true && false;
// false
var_dump($bool);


$bool = true and false;
// true
var_dump($bool);

Source: https://www.php.net/manual/zh/language.operators.precedence.php#117390

instanceof operator

Have you ever written code like the following?

<?php

class A {

}

$A = new A();

var_dump((! $A instanceof A));

// 其实不用担心,因为 instanceof 的优先级要高于 ! ,你可以放心的使用,
// 不必添加括号,让他们看起来是一个表达式,但是在复杂的情况下例外。
var_dump(! $A instanceof A);

When you need to instanceof the result of the operation of ! , because the precedence of the instanceof , you do not need to add a parenthesis outside them to indicate that this is a set of expressions, but then Exceptions in complex situations.

Interesting usage of array_map

Usually, I use array_map to process an array and let him return a new array. Of course, its usefulness is like this, but in addition to this basic usage, it actually has some interesting usages, and these usages are all Exists in the PHP manual.

Multiple array usage

Usually you use it like this.

<?php

$arr1 = ['apple', 'orange', 'mango', 'pear'];
$newArr1 = array_map('strtoupper',$arr1);

This is just a simple 🌰, it will convert all values to uppercase. Then take a look at the usage below and guess what will be printed?

<?php

$arr1 = ['apple', 'orange', 'mango', 'pear'];
$arr2 = ['cauliflower', 'lettuce', 'kale', 'romaine'];

function map1($a, $b){
    var_dump($a, $b);
  // apple   cauliflower
  // orange  lettuce
  // mango   kale
  // pear    romaine
}

array_map('map1', $arr1, $arr2);

As map1 method, the values in $arr1 and $arr2 will be sequentially traversed map1 , according to the definition of the manual: If multiple arrays have different lengths, that is, the short array will be filled to the long The array is the same as .

Improper use of native functions will be slower than you think

Array_unique, array_merge, etc., if used incorrectly, will be slower than you think, or even much slower, far inferior to foreach.

In the following answer, the time complexity of some array_* methods in PHP is listed
performance - List of Big-O for PHP functions - Stack Overflow

Be careful with comparisons in the code

The following comparison will return true , can't you believe it?

Because both md5 values have a start of '0e', the PHP type understands these strings as scientific symbols. By definition, any power of 0 is 0, so it will be true here, so when you determine the type of a variable, you'd better use === (constant equal) for comparison.

<?php

$a = md5('240610708');// 0e462097431906509019562988736854
$b = md5('QNKCDZO'); // 0e830400451993494058024219903391

var_dump($a == $b); // true

Note that when you are considering using md5 to store passwords, you should give up this idea and use password_hash series methods instead.

From: https://www.php.net/manual/zh/function.md5.php#123392

Disable the insecure eval method in PHP

As we all know, in php, the eval method can execute arbitrary PHP code. If it is not handled properly and used by users, it may cause security vulnerabilities, so it is best to find a way to disable it. When it comes to disabling php functions, you should think disable_functions parameter in php.ini can be used to disable PHP functions, and some high-risk functions will also be disabled in some integrated environments to reduce risks.

However, this configuration item cannot disable the eval function, because according to the definition of official documents, eval is not a function. It is the same as echo and these special methods. It is a grammatical structure, so disable_functions cannot be used to disable it. , There are also require, list, array, print, unset, include, isset, empty, die, exit, etc. These are grammatical structures, not functions. If you use function_exists judge, they will all return false

If you really need to disable eval, you have to install some third-party extensions, such as mk-j/PHP_diseval_extension

Reference: https://www.php.net/manual/zh/functions.variable-functions.php#functions.variable-functions

Convert any type to null

It sounds useless but you can do it.

<?php

$a = 'Hi!';
// 在 PHP 7.2 以下,这行代码会返回 null,7.2 ~ 7.4 会返回 NULL,但是会提示被遗弃,
// 8.0 开始,将不再支持
var_dump((unset)$a);
var_dump($a);

In addition, you can also use the settype function
Reference: https://www.php.net/settype

Reference: https://www.php.net/manual/zh/function.unset.php#example-5601

isset and unset support multiple parameters at the same time

Unset supports multiple parameters, presumably most people know it, but isset also supports it.

<?php

var_dump(isset($a, $b, $c));

unset($a, $b, $c);

You don’t need to worry that these variables are not set. They are safe here and will not report an error. When there are multiple variables in isset, null will be returned only when true . When one is encountered If it does not exist, it will return immediately.

Reference: https://www.php.net/isset

Quickly look up a function or class or grammar reference

When you want to query a php method or object or grammar, you do not need to open the php manual to search, you only need to https://php.net/<keyword> , and you don’t need to care about the size, such as Below these links.

Use reflection to call protected or private class methods

If you want to prevent a method from being externally visible or subclasses visible, you can use protected or private keywords to modify these classes, but sometimes we want to call these methods externally, what should we do? Can it only be changed to public? If this is our own code, of course we can do this, but if it is the imported external code, it may not be easy to modify it directly.

Now, we can use reflection to call these methods externally, now let's define a Lisa class

<?php

class Lisa
{
    public function name()
    {
        return 'Lisa';
    }

    protected function age()
    {
        return 22;
    }

    private function weight()
    {
        return 95;
    }
  
    private static function eat(){
        return 1;
    }
}

Under normal circumstances, we have no way to directly call the age and weight methods. Now, we use reflection to call them.

<?php
// ...
$reflectionClass = new ReflectionClass('Lisa');
$ageMethod = $reflectionClass->getMethod('age'); // 获取 age 方法
$ageMethod->setAccessible(true); // 设置可见性
// 调用这个方法,需要传入对象作为上下文
$age = $ageMethod->invoke($reflectionClass->newInstance());
var_dump($age);// 22

The above code looks a bit cumbersome, there is a simpler way.

<?php
// ...
$reflectionClass = new ReflectionClass('Lisa');
$weightMethod = $reflectionClass->getMethod('weight');// 获取 weight 方法
// 获取一个闭包,然后调用,同样需要传入对象作为上下文,后面调用的地方就可以传入参数
$weight = $weightMethod->getClosure($reflectionClass->newInstance())();
var_dump($weight);

Call static method

<?php
// ...
$reflectionClass = new ReflectionClass('Lisa');
$eatMethod = $reflectionClass->getMethod('eat');
$eatMethod->setAccessible(true);
$eat = $eatMethod->invoke(null); // 如果是一个静态方法,你可以传递一个 null
var_dump($eat);

Similarly, class members can also be modified using reflection.
Reference: https://www.php.net/manual/zh/class.reflectionproperty.php

Instantiate a class, but bypass its construction method

Have you ever thought about it this way? Instantiate a class, but don't want to call its constructor (__construct), you can also use reflection here.

<?php

class Dog
{
    private $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

$dogClass = new ReflectionClass('Dog');
// 创建一个新实例,但是不调用构造方法
$dogInstance = $dogClass->newInstanceWithoutConstructor();
var_dump($dogInstance->getName()); // null

If your environment cannot use reflection, you can try another cool method, which is to use deserialization. You can refer to package doctrine/instantiator-Packagist

Reference: https://www.php.net/manual/zh/reflectionclass.newinstancewithoutconstructor.php

Get all parent classes of a class

Use class_parents to get all the parent classes of a class and support automatic loading.

<?php

class A{}
class B extends A{}
class C extends B{}
class D extends C{}

var_dump(class_parents('D'));
/*
array(3) {
  'C' =>
  string(1) "C"
  'B' =>
  string(1) "B"
  'A' =>
  string(1) "A"
}
*/

Reference: https://www.php.net/manual/zh/function.class-parents.php

Interesting increment and decrement

Increasing and decreasing cannot scope bool values

Increment and decrement cannot be used on false, but += and -= can

<?php

$a = false;

++$a;

var_dump($a);// false

$a++;

var_dump($a);// false

--$a;

var_dump($a);// false

$a--;

var_dump($a);// false

$a-= 1;

var_dump($a);// -1

$a+= 1;// 因为前面改变了,变成了 -1,所以下面是 0 ,请不要在这里疑惑

var_dump($a);// 0

Increasing can be scoped to NULL, but decrementing cannot

<?php

$a = null;
++$a;
var_dump($a); //1

$a = null;
--$a;
var_dump($a); // null

Increasing can work on letters, but decreasing can’t

When ay increases, the letter will increase by one, but when z, it will return to aa, the loop is like this, but it can only increase, not decrease

<?php

$a = 'a';
++$a;
var_dump($a); // b

$a = 'z';
++$a;
var_dump($a); // aa

$a = 'A';
++$a;
var_dump($a); // B

$a = 'Z';
++$a;
var_dump($a); // AA

Mixing increasing numbers and letters

Now you can also mix letters and numbers, like this:

>>> $a = 'A1'
=> "A1"
>>> ++$a
=> "A2"
>>> ++$a
=> "A3"
>>> $a = '001A'
=> "001A"
>>> ++$a
=> "001B"
>>> ++$a
=> "001C"
>>> $a = 'A001'
=> "A001"
>>> ++$a
=> "A002"
>>> ++$a
=> "A003"

But please pay attention to some unexpected situations, such as this.

>>> $a = '9E0'
=> "9E0"
>>> ++$a
=> 10.0

This is because 9E0 is treated as a string representation of a floating-point number, is treated as 9*10^0 by PHP, is evaluated as 9 and then is incremented.

Reference source: https://www.php.net/manual/zh/language.operators.increment.php#109621

Please pay attention to your nested coercion, otherwise he will have an accident

<?php

var_dump(TRUE === (boolean) (array) (int) FALSE);// true

var_dump((array) (int) FALSE);

Because when FALSE is converted to a number, it is 0, and then converted to an array, it becomes [0] , so when converted to a boolean, it will return true, because the array is not empty, and [0] != []

Reference: https://www.php.net/manual/zh/language.types.type-juggling.php#115373

Compare numbers and strings in the higher version

Since PHP 8.0.

Non-strict comparisons between numbers and non-numeric strings will now first convert the number to a string, and then compare the two strings. The comparison between numbers and strings in digital form is still performed as before. Note that this means that 0 == "not-a-number" will now be considered false.
ComparisonBeforeAfter
0 == "0"truetrue
0 == "0.0"truetrue
0 == "foo"truefalse
0 == ""truefalse
42 == " 42"truetrue
42 == "42foo"truefalse

Reference: https://www.php.net/manual/zh/migration80.incompatible.php#migration80.incompatible.core

Arrays can also be compared directly

You can directly use == compare two arrays with the same key-value pair. If this is not an associative array, then you must ensure that the order of the values corresponds. If it is an associative array, you don't need to worry.

>>> $b = [1,2,3,4]
=> [
     1,
     2,
     3,
     4,
   ]
>>> $a = [1,2,3,4]
=> [
     1,
     2,
     3,
     4,
   ]
>>> $a == $b
=> true

// 注意,他不会比较类型。

>>> $a = [0,1,2,3,4]
=> [
     0,
     1,
     2,
     3,
     4,
   ]
>>> $b = [false,1,2,3,4]
=> [
     false,
     1,
     2,
     3,
     4,
   ]
>>> $a == $b
=> true

// 如果你要比较类型,你应该使用 ===

>>> $a === $b
=> false

Unordered comparison:
In the following list, using == will return true because their values are equal, but the order is different, but if you use ===, the type will be returned, because === will consider the key value order and data type .

>>> $a = ['name'=>'Jack','sex'=>1,'age'=>18];
=> [
     "name" => "Jack",
     "sex" => 1,
     "age" => 18,
   ]
>>> $b = ['name'=>'Jack','age'=>18,'sex'=>1];
=> [
     "name" => "Jack",
     "age" => 18,
     "sex" => 1,
   ]
>>> $a == $b
=> true
>>> $a === $b
=> false
>>>

Source: PHP: Array Operators-Manual

Merge array

Arrays can also be added (+) to merge arrays. Use array_merge to merge arrays. You can add two arrays. You must know it, but in fact, the + sign is also possible. Although both are merged arrays, these two methods Each is different. + more like a replacement.

1. When using array_merge to merge non-associative arrays, duplicate items will not be filtered, + Yes (more like replacement)

>>> $a = [1,2,3]
=> [
     1,
     2,
     3,
   ]
>>> $b = [2,3,4]
=> [
     2,
     3,
     4,
   ]
>>> array_merge($a,$b)
=> [
     1,
     2,
     3,
     2,
     3,
     4,
   ]
>>> $a + $b
=> [
     1,
     2,
     3,
   ]

2. When using array_merge to merge associative arrays, if the key is repeated, the value of the last array will be retained, while using + will retain the value under the first key.

>>> $a = ['name'=>'Jack','sex'=>1,'age'=>18];
=> [
     "name" => "Jack",
     "sex" => 1,
     "age" => 18,
   ]
>>> $b = ['name'=>'Jack','age'=>'18','sex'=>'1'];
=> [
     "name" => "Jack",
     "age" => "18",
     "sex" => "1",
   ]
>>> array_merge($a, $b)
=> [
     "name" => "Jack",
     "sex" => "1",
     "age" => "18",
   ]
>>> $a + $b
=> [
     "name" => "Jack",
     "sex" => 1,
     "age" => 18,
   ]

3. When there are numeric keys in the associative array, array_merge will reset the numeric keys, and + will not

>>> $a = ['name'=>'Jack','sex'=>1,'age'=>18];
=> [
     "name" => "Jack",
     "sex" => 1,
     "age" => 18,
   ]
>>> $b = ['name'=>'Jack','age'=>'18','sex'=>'1','10'=>'hi'];
=> [
     "name" => "Jack",
     "age" => "18",
     "sex" => "1",
     10 => "hi",
   ]
>>> array_merge($a,$b)
=> [
     "name" => "Jack",
     "sex" => "1",
     "age" => "18",
     //👇 注意这里
     0 => "hi",
   ]
>>> $a + $b
=> [
     "name" => "Jack",
     "sex" => 1,
     "age" => 18,
     //👇 注意这里
     10 => "hi",
   ]

Let's use a picture to summarize it.

Screenshot from 2015-11-13 00:34:21

Image source: array_merge vs array_replace vs + (plus aka union) in PHP | SOFTonSOFA

the end

  • Most of the content in the article is collected from the Internet. I have tried my best to verify its authenticity, but there may be some omissions. If there are any, please feel free to enlighten me.
  • In addition, if the content in the article violates your rights, please contact me to deal with it.
  • You can also click on the source link in the article to learn more about it.

reference


唯一丶
23.1k 声望8.7k 粉丝

友情链接