Home >Backend Development >PHP Tutorial >The 10 most common mistakes in PHP programming, PHP programming 10_PHP tutorial
PHP is a very popular open source server-side scripting language used on most websites you see on the World Wide Web All are developed using php. This article will introduce you to the 10 most common problems in PHP development. I hope it can be helpful to your friends.
In the foreach loop, if we need to change the iterated elements or to improve efficiency, using references is a good way:
1 2 3 4 5 |
$arr = array (1, 2, 3, 4);
foreach ( $arr as & $value ) {
$value = $value * 2;
}
// $arr is now array(2, 4, 6, 8)
|
There is a question that many people will be confused about. After the loop ends, $value is not destroyed. $value is actually a reference to the last element in the array. If you don't know this in the subsequent use of $value, it will cause some inexplicable errors:) Take a look below. This code:
1 2 3 4 5 6 7 8 |
$array = [1, 2, 3];
echo implode( ',' , $array ), "n" ;
foreach ( $array as & $value ) {} // by reference
echo implode( ',' , $array ), "n" ;
foreach ( $array as $value ) {} // by value (i.e., copy)
echo implode( ',' , $array ), "n" ;
|
The results of the above code are as follows:
1 2 3 |
1,2,3
1,2,3
1,2,2
|
Did you guess it right? Why is this result?
Let’s analyze it. After the first loop, $value is a reference to the last element in the array. The second cycle begins:
In summary, the final result is 1,2,2
The best way to avoid this error is to use the unset function to destroy the variable immediately after the loop:
1 2 3 4 5 |
$arr = array (1, 2, 3, 4);
foreach ( $arr as & $value ) {
$value = $value * 2;
}
unset( $value ); // $value no longer references $arr[3]
|
For the isset() function, false will be returned when the variable does not exist, and false will be returned when the variable value is null. This behavior can easily confuse people. . . Look at the code below:
1 2 3 4 |
$data = fetchRecordFromStorage( $storage , $identifier );
if (!isset( $data [ 'keyShouldBeSet' ]) {
// do something here if 'keyShouldBeSet' is not set
}
|
The person who wrote this code may have intended that if $data['keyShouldBeSet'] is not set, the corresponding logic will be executed. But the problem is that even if $data['keyShouldBeSet'] has been set, but the set value is null, the corresponding logic will still be executed, which is not in line with the original intention of the code.
Here is another example:
1 2 3 4 5 6 7 8 9 |
if ( $_POST [ 'active' ]) {
$postData = extractSomething( $_POST );
}
// ...
if (!isset( $postData )) {
echo 'post not active' ;
}
|
The above code assumes that $_POST['active'] is true, then $postData should be set, so isset($postData) will return true. Conversely, the above code assumes that the only way isset($postData) returns false is if $_POST['active'] also returns false.
Is this really the case? Of course not!
Even if $_POST['active'] returns true, $postData may be set to null, in which case isset($postData) will return false. This goes against the intent of the code.
If the purpose of the above code is only to detect whether $_POST['active'] is true, the following implementation would be better:
1 2 3 4 5 6 7 8 9 |
if ( $_POST [ 'active' ]) {
$postData = extractSomething( $_POST );
}
// ...
if ( $_POST [ 'active' ]) {
echo 'post not active' ;
}
|
To determine whether a variable is actually set (to distinguish between unset and set values to null), the array_key_exists() function may be better. Refactor the first example above as follows:
1 2 3 4 |
$data = fetchRecordFromStorage( $storage , $identifier );
if (! array_key_exists ( 'keyShouldBeSet' , $data )) {
// do this if 'keyShouldBeSet' isn't set
}
|
In addition, combined with the get_defined_vars() function, we can more reliably detect whether the variable is set in the current scope:
1 2 3 |
if ( array_key_exists ( 'varShouldBeSet' , get_defined_vars())) {
// variable $varShouldBeSet exists in current scope
}
|
Consider the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Config
{
private $values = [];
public function getValues() {
return $this ->values;
}
}
$config = new Config();
$config ->getValues()[ 'test' ] = 'test' ;
echo $config ->getValues()[ 'test' ];
|
Running the above code will output the following content:
1 |
PHP Notice: Undefined index: test in /path/to/my/script.php on line 21
|
What’s the problem? The problem is that the above code confuses return values and return references. In PHP, unless you explicitly specify a return reference, PHP returns a value for an array, which is a copy of the array. Therefore, when the above code assigns a value to the returned array, it actually assigns a value to the copied array, not the original array.
1 2 3 4 5 6 7 |
// getValues() returns a COPY of the $values array, so this adds a 'test' element
// to a COPY of the $values array, but not to the $values array itself.
$config ->getValues()[ 'test' ] = 'test' ;
// getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't
// contain a 'test' element (which is why we get the "undefined index" message).
echo $config ->getValues()[ 'test' ];
|
Here is a possible solution to output the copied array instead of the original array:
1 2 3 |
$vals = $config ->getValues();
$vals [ 'test' ] = 'test' ;
echo $vals [ 'test' ];
|
If you just want to change the original array, that is, return the array reference, how should you deal with it? The way is to display the specified return reference:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Config
{
private $values = [];
// return a REFERENCE to the actual $values array
public function &getValues() {
return $this ->values;
}
}
$config = new Config();
$config ->getValues()[ 'test' ] = 'test' ;
echo $config ->getValues()[ 'test' ];
|
After modification, the above code will output test as you expect.
Let’s look at another example that will make you even more confused:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Config
{
private $values ;
// using ArrayObject rather than array
public function __construct() {
$this ->values = new ArrayObject();
}
public function getValues() {
return $this ->values;
}
}
$config = new Config();
$config ->getValues()[ 'test' ] = 'test' ;
echo $config ->getValues()[ 'test' ];
|
If you think that the “Undefined index” error will be output like above, then you are wrong. The code will output "test" normally. The reason is that PHP returns objects by reference by default, not by value.
In summary, when we use a function to return a value, we need to figure out whether it is a value return or a reference return. For objects in PHP, the default is to return by reference, and arrays and built-in basic types are returned by value by default. This should be distinguished from other languages (many languages pass arrays by reference).
Like other languages, such as java or C#, it is a better solution to use getters or setters to access or set class properties. Of course, PHP does not support it by default and you need to implement it yourself:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Config
{
private $values = [];
public function setValue( $key , $value ) {
$this ->values[ $key ] = $value ;
}
public function getValue( $key ) {
return $this ->values[ $key ];
}
}
$config = new Config();
$config ->setValue( 'testKey' , 'testValue' );
echo $config ->getValue( 'testKey' ); // echos 'testValue'
|
The above code allows the caller to access or set any value in the array without giving the array public access. How does it feel:)
It is not uncommon to find code similar to the following in PHP programming:
1 2 3 4 5 |
$models = [];
foreach ( $inputValues as $inputValue ) {
$models [] = $valueRepository ->findByValue( $inputValue );
}
|
Of course there is nothing wrong with the above code. The problem is that during the iteration process, $valueRepository->findByValue() may execute the sql query every time:
1 |
$result = $connection ->query( "SELECT `x`,`y` FROM `values` WHERE `value`=" . $inputValue );
|
If it is iterated 10,000 times, then you have executed 10,000 SQL queries respectively. If such a script is called in a multi-threaded program, it is likely that your system will hang. . .
During the process of writing code, you should know when to execute the sql query, and try to retrieve all the data in one sql query.
There is a business scenario where you are likely to make the above mistakes. Suppose a form submits a series of values (assumed to be IDs), and then in order to retrieve the data corresponding to all IDs, the code will traverse the IDs and execute sql queries for each ID respectively. The code is as follows:
1 2 3 4 5 |
$data = [];
foreach ( $ids as $id ) {
$result = $connection ->query( "SELECT `x`, `y` FROM `values` WHERE `id` = " . $id );
$data [] = $result ->fetch_row();
}
|
But the same purpose can be accomplished more efficiently in a sql, the code is as follows:
1 2 3 4 5 6 7 |
$data = [];
if ( count ( $ids )) {
$result = $connection ->query( "SELECT `x`, `y` FROM `values` WHERE `id` IN (" . implode( ',' , $ids ));
while ( $row = $result ->fetch_row()) {
$data [] = $row ;
}
}
|
It is definitely more efficient to obtain multiple records in one SQL query than to obtain one record in each query. However, if you are using the MySQL extension in PHP, obtaining multiple records at one time is likely to cause memory overflow.
We can write code to experiment (test environment: 512MB RAM, MySQL, php-cli):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// connect to mysql
$connection = new mysqli( 'localhost' , 'username' , 'password' , 'database' );
// create table of 400 columns
$query = 'CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT' ;
for ( $col = 0; $col < 400; $col ++) {
$query .= ", `col$col` CHAR(10) NOT NULL" ;
}
$query .= ');' ;
$connection ->query( $query );
// write 2 million rows
for ( $row = 0; $row < 2000000; $row ++) {
$query = "INSERT INTO `test` VALUES ($row" ;
for ( $col = 0; $col < 400; $col ++) {
$query .= ', ' . mt_rand(1000000000, 9999999999);
}
$query .= ')' ;
$connection ->query( $query );
}
|
Now let’s look at resource consumption:
1 2 3 4 5 6 7 8 9 |
// connect to mysql
$connection = new mysqli( 'localhost' , 'username' , 'password' , 'database' );
echo "Before: " . memory_get_peak_usage() . &quot;n&quot; ;
$res = $connection ->query( 'SELECT `x`,`y` FROM `test` LIMIT 1' );
echo "Limit 1: " . memory_get_peak_usage() . &quot;n&quot; ;
$res = $connection ->query( 'SELECT `x`,`y` FROM `test` LIMIT 10000' );
echo "Limit 10000: " . memory_get_peak_usage() . &quot;n&quot; ;
|
The output results are as follows:
1 2 3 |
Before: 224704
Limit 1: 224704
Limit 10000: 224704
|
Judging from the memory usage, everything seems to be normal. To be more sure, try to get 100000 records at a time. As a result, the program gets the following output:
1 2 |
PHP Warning: mysqli::query(): (HY000/2013):
Lost connection to MySQL server during query in /root/test.php on line 11
|
What’s going on?
The problem lies in the way PHP’s mysql module works. The mysql module is actually a proxy for libmysqlclient. While querying to obtain multiple records, these records will be stored directly in memory. Since this memory is not managed by the memory module of PHP, the value obtained by calling the memory_get_peak_usage() function is not the actual memory usage value, so the above problem occurs.
We can use mysqlnd instead of mysql. Mysqlnd is compiled into PHP's own extension, and its memory usage is controlled by the PHP memory management module. If we use mysqlnd to implement the above code, it will reflect the memory usage more realistically:
1 2 3 |
Before: 232048
Limit 1: 324952
Limit 10000: 32572912
|
To make matters worse, according to the official documentation of PHP, the memory used by the mysql extension to store query data is twice that of mysqlnd, so the original code uses about twice the memory shown above.
In order to avoid such problems, you can consider completing the query in several times to reduce the amount of data in a single query:
1 2 3 4 5 6 7 8 |
$totalNumberToFetch = 10000;
$portionSize = 100;
for ( $i = 0; $i <= ceil ( $totalNumberToFetch / $portionSize ); $i ++) {
$limitFrom = $portionSize * $i ;
$res = $connection ->query(
"SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize" );
}
|
Based on the error 4 mentioned above, it can be seen that in the actual coding process, a balance must be achieved to not only meet the functional requirements, but also ensure performance.
In PHP programming, you will encounter some problems when dealing with non-ASCII characters. You must deal with them very carefully, otherwise you will get errors all over the place. Take a simple example, strlen($name), if $name contains non-ASCII characters, the results will be somewhat unexpected. Here are some suggestions to avoid such problems:
Here is an article recommended to introduce this type of problem in more detail: UTF-8 Primer for PHP and MySQL
$_POST in PHP does not always contain the data submitted by form POST. Suppose we send a POST request to the server via the jQuery.ajax() method:
1 2 3 4 5 6 7 |
// js
$.ajax({
url: 'http://my.site/some/path' ,
method: 'post' ,
data: JSON.stringify({a: 'a' , b: 'b' }),
contentType: 'application/json'
});
|
Pay attention to contentType: ‘application/json’ in the code. We are sending data in json data format. On the server side, we just output the $_POST array:
1 2 |
// php
var_dump( $_POST );
|
You will be surprised to find that the result is as follows:
1 |
array (0) { }
|
Why is this the result? Where did our json data {a: ‘a’, b: ‘b’} go?
The answer is that PHP only parses HTTP requests whose Content-Type is application/x-www-form-urlencoded or multipart/form-data. The reason for this is for historical reasons. When PHP first implemented $_POST, the above two types were the most popular. Therefore, although some types (such as application/json) are very popular now, automatic processing is still not implemented in PHP.
Since $_POST is a global variable, changing $_POST will be globally effective. Therefore, for requests whose Content-Type is application/json, we need to manually parse the json data and then modify the $_POST variable.
1 2 |
// php
$_POST = json_decode( file_get_contents ( 'php://input' ), true);
|
At this point, if we output the $_POST variable, we will get the output we expect:
1 |
array (2) { [ "a" ]=> string(1) "a" [ "b" ]=> string(1) "b" }
|
Take a look at the code below and guess what will be output:
1 2 3 |
for ( $c = 'a' ; $c <= 'z' ; $c ++) {
echo $c . &quot;n&quot; ;
}
|
If your answer is to output 'a' to 'z', then you will be surprised to find that your answer is wrong.
Yes, the above code does output 'a' to 'z', but in addition, it also outputs 'aa' to 'yz'. Let's analyze why this is the result.
There is no char data type in PHP, only string type. Understand this, then increment 'z', and the result is 'aa'. Regarding string size comparison, those who have studied C should know that 'aa' is smaller than 'z'. This also explains why there is the above output result.
If we want to output 'a' to 'z', the following implementation is a good way:
1 2 3 |
for ( $i = ord( 'a' ); $i <= ord( 'z' ); $i ++) {
echo chr ( $i ) . &quot;n&quot; ;
}
|
Or this is also OK:
1 2 3 4 5 |
$letters = range( 'a' , 'z' );
for ( $i = 0; $i < count ( $letters ); $i ++) {
echo $letters [ $i ] . &quot;n&quot; ;
}
|
Although ignoring coding standards will not lead to errors or bugs, it is still important to follow certain coding standards.
Without unified coding standards, your project will have many problems. The most obvious thing is that your project code is inconsistent. Even worse, your code will be harder to debug, extend, and maintain. This also means that your team's efficiency will be reduced, including doing a lot of meaningless work.
For PHP developers, it is relatively lucky. Because there is a PHP Coding Standard Recommendation (PSR), which consists of the following 5 parts:
PSR was originally created and followed by several large groups in the PHP community. Zend, Drupal, Symfony, Joomla and other platforms have contributed to and adhere to this standard. Even PEAR wanted to become a standard in the early years, but now it has joined the PSR camp.
In some cases, it doesn’t matter what coding standard you use, as long as you use a coding style and stick with it. But following the PSR standard is a good idea, unless you have some special reason to make one yourself. Now more and more projects are beginning to use PSR, and most PHP developers are also using PSR. Therefore, using PSR will make new members of your team familiar with the project faster and will be more comfortable when writing code.
Some PHP developers like to use the empty() function to make Boolean judgments on variables or expressions, but it can be confusing in some cases.
First, let’s take a look at the array Array and array object ArrayObject in PHP. There seems to be no difference, they are all the same. Is this really the case?
1 2 3 4 5 6 |
// PHP 5.0 or later:
$array = [];
var_dump( empty ( $array )); // outputs bool(true)
$array = new ArrayObject();
var_dump( empty ( $array )); // outputs bool(false)
// why don't these both produce the same output?
|
To make things a little more complicated, take a look at the following code:
1 2 3 4 5 |
// Prior to PHP 5.0:
$array = [];
var_dump( empty ( $array )); // outputs bool(false)
$array = new ArrayObject();
var_dump( empty ( $array )); // outputs bool(false)
|
Unfortunately, the above method is very popular. For example, in Zend Framework 2, ZendDbTableGateway does this when calling the current() method on the TableGateway::select() result set to return a data set. Developers can easily fall into this trap.
To avoid these problems, the last resort to check if an array is empty is to use the count() function:
1 2 3 4 5 |
// Note that this work in ALL versions of PHP (both pre and post 5.0):
$array = [];
var_dump( count ( $array )); // outputs int(0)
$array = new ArrayObject();
var_dump( count ( $array )); // outputs int(0)
|
By the way, because PHP considers the value 0 to be a Boolean value false, the count() function can be directly used in the conditional judgment of the if conditional statement to determine whether the array is empty. In addition, the count() function has a complexity of O(1) for arrays, so using the count() function is a wise choice.
Let’s look at another example where using the empty() function is dangerous. It is also dangerous when using the empty() function in combination with the magic method __get(). Let's define two classes, each with a test attribute.
First we define the Regular class, which has a test attribute:
1 2 3 4 |
class Regular
{
public $test = 'value' ;
}
|
Then we define the Magic class and use the __get() magic method to access its test attribute:
1 2 3 4 5 6 7 8 9 10 11 |
class Magic
{
private $values = [ 'test' => 'value' ];
public function __get( $key )
{
if (isset( $this ->values[ $key ])) {
return $this ->values[ $key ];
}
}
}
|
Okay. Let’s now see what happens when accessing the test attribute of each class:
1 2 3 4 |
$regular = new Regular();
var_dump( $regular ->test); // outputs string(4) "value"
$magic = new Magic();
var_dump( $magic ->test); // outputs string(4) "value"
|
So far, everything is normal and it doesn’t make us feel confused.
But what happens if you use the empty() function on the test attribute?
1 2 |
var_dump( empty ( $regular ->test)); // outputs bool(false)
var_dump( empty ( $magic ->test)); // outputs bool(true)
|
Is the result surprising?
Unfortunately, if a class uses the magic __get() function to access the value of a class attribute, there is no easy way to check whether the attribute value is empty or does not exist. Outside the class scope, you can only check if a null value is returned, but this does not necessarily mean that the corresponding key is not set, because the key value can be set to null .
In contrast, if we access a non-existent property of the Regular class, we will get a Notice message similar to the following:
1 2 3 4 |
Notice: Undefined property: Regular:: $nonExistantTest in /path/to/test.php on line 10
Call Stack:
0.0012 234704 1. {main}() /path/to/test.php:0
|
Therefore, for the empty() function, we must use it carefully, otherwise the results will be unexpected and even potentially misleading you.
By: Eddy Translated from: 10 Most Common PHP Mistakes
function zh($x){
$num=strlen($x);
$str='abcdefghij';
for($i=0;$i&amp;lt ;$num;$i++){
$arr[]=substr($x,$i,1);
$zhstr.=$str[$arr[$i]];
}
return $zhstr;
}
$str=zh('1563'); //Set initial value
print($str);
?>
This is a problem with table locking. You lock the table directly when reading, and the next user will have to wait temporarily. But it’s generally not handled this way. This will make the website experience very poor.