Analysis of the 10 most common errors in PHP programming

Source: Internet
Author: User
Tags coding standards form post
PHP is a great web development language and flexible language, but we can see some mistakes made by php programmers. I made the following list to list the 10 errors that PHP programmers often make, most of which are related to security. Let's see if you have made several friends who are learning PHP at present. there will always be various problems in daily program development projects, this article will introduce you to the 10 most common problems in PHP development and hope to help you.

Error 1: the pointer is left after the foreach loop.

In a foreach loop, if we need to change the iteration elements or use references to improve efficiency, it is a good solution:

$arr = array(1, 2, 3, 4); foreach ($arr as &$value) {  $value = $value * 2; } // $arr is now array(2, 4, 6, 8)

There is a problem that many people will be confused. After the loop ends, the value is not destroyed, and the value is actually a reference to the last element in the array. in this way, if you do not know this in future use of $ value, will cause some inexplicable errors :) Look at the following code:

$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 running result of the above code is as follows:

1,2,3 1,2,3 1,2,2

You guessed it? Why is this result?

Let's analyze it. After the first loop, $ value is the reference of the last element in the array. The second cycle begins:

Step 1: copy arr [0] to value (note that the value is a reference of arr [2]), and then the array is changed to [1, 2, 1].
Step 2: copy arr [1] to value. then, the array is changed to [1, 2].
Step 3: copy arr [2] to value. then, the array is changed to [1, 2].
In conclusion, the final result is, 2.

The best way to avoid this error is to use the unset function to destroy the variable immediately after the loop:

$arr = array(1, 2, 3, 4); foreach ($arr as &$value) {   $value = $value * 2; } unset($value);  // $value no longer references $arr[3]

Error 2: incorrect understanding of isset () function behavior

For the isset () function, if the variable does not exist, false is returned. if the variable value is null, false is returned. This kind of behavior can easily confuse people... See the following code:

$data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) {   // do something here if 'keyShouldBeSet' is not set }

The human intention of writing this code may be that if data ['keyshouldbeset'] is not set, the corresponding logic is 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 code's intention.

The following is another example:

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) returns true. Otherwise, the code above assumes that the only way for isset (postData) to return false is that $ _ POST ['active'] also returns false.

Is that true? Of course not!

Even if POST ['active'] returns true, postData may be set to null. in this case, isset ($ postData) returns false. This does not conform to the code's intention.

If the above code is only intended to check whether $ _ POST ['active'] is true, the following implementation will be better:

if ($_POST['active']) {   $postData = extractSomething($_POST); }  // ...  if ($_POST['active']) {   echo 'post not active'; }

The array_key_exists () function may be better at determining whether a variable is actually set (distinguish unset and set to null. The first example of refactoring is as follows:

$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 check whether variables are set in the current scope:

if (array_key_exists('varShouldBeSet', get_defined_vars())) {   // variable $varShouldBeSet exists in current scope }

Error 3: mixed return values and return references

Consider the following code:

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:

PHP Notice: Undefined index: test in /path/to/my/script.php on line 21

What is the problem? The problem is that the above code obfuscated the return value and return reference. In PHP, unless you specify the returned reference, PHP returns a value for the array, that is, copying the array. Therefore, the code above assigns a value to the returned array, which is to assign a value to the copy array instead of the original array.

// 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'];

The following is a possible solution: output the copied array instead of the original array:

$vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];

If you want to change the original array, that is, to reverse the array reference, what should you do? You can simply display the specified return reference:

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 the transformation, the above code will output test as expected.

Let's look at an example that will make you more confused:

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 want to output the "Undefined index" error like above, you will be wrong. The code will output "test" normally ". The reason is that PHP returns objects by reference by default, rather than by value.

To sum up, when using the function return value, we need to find out whether the value is returned or the reference is returned. In PHP, objects are returned by reference by default, and arrays and built-in basic types are returned by value by default. This should be different from other languages (many languages use references for arrays ).

Like other languages, such as java or C #, using getter or setter to access or set class attributes is a better solution. of course, PHP does not support this by default and needs to be implemented by itself:

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 code above allows the caller to access or set any value in the array without the array public access permission. How do you feel :)

Error 4: execute SQL query in a loop

In PHP programming, it is not uncommon to find code similar to the following:

$models = [];  foreach ($inputValues as $inputValue) {   $models[] = $valueRepository->findByValue($inputValue); }

Of course, the above code is correct. The problem is that during the iteration, $ valueRepository-> findByValue () may execute an SQL query every time:

$result = $connection->query("SELECT `x`,`y` FROM `values` WHERE `value`=" . $inputValue);

If you iterate 10000 times, you will execute 10000 SQL queries respectively. If such a script is called in a multi-threaded program, your system may be suspended...

During code writing, you should know when to execute an SQL query and retrieve all the data at the same time.

There is a business scenario where you are likely to make the above mistakes. Assume that a form submits a series of values (assumed as IDs). in order to retrieve the data corresponding to all IDs, the code traverses IDs and executes SQL queries for each ID. the code is as follows:

$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 completed more efficiently in an SQL statement. The code is as follows:

$data = []; if (count($ids)) {   $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` IN (" . implode(',', $ids));   while ($row = $result->fetch_row()) {     $data[] = $row;   } }

Error 5: memory usage inefficiency and illusion

Obtaining multiple records in one SQL query is more efficient than obtaining one record in each query. However, if you use mysql extension in php, therefore, obtaining multiple records at a time may cause memory overflow.

We can write code for the experiment (test environment: 512 mb ram, MySQL, php-cli ):

// 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 take a look at the resource consumption:

// connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); echo "Before: " . memory_get_peak_usage() . "\n";  $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1'); echo "Limit 1: " . memory_get_peak_usage() . "\n";  $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000'); echo "Limit 10000: " . memory_get_peak_usage() . "\n";

The output result is as follows:

Before: 224704 Limit 1: 224704 Limit 10000: 224704

According to the memory usage, everything looks normal. To be more specific, try to get 100000 records at a time. The result program gets the following output:

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 how the mysql module of php works. the mysql module is actually a proxy of libmysqlclient. When querying and retrieving multiple records, these records are directly stored in the memory. Because this memory is not managed by the php memory module, the value obtained by calling the memory_get_peak_usage () function is not actually the memory value, so the above problem occurs.

We can use mysqlnd to replace mysql. mysqlnd is compiled as 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:

Before: 232048 Limit 1: 324952 Limit 10000: 32572912

Even worse, according to official php documents, mysql extended storage and query data use twice the memory of mysqlnd, so the original code uses about twice the memory shown above.

To avoid such problems, you can consider completing queries several times to reduce the data volume of a single query:

$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"); }

In connection with error 4 mentioned above, we can see that in the actual encoding process, a balance must be achieved to meet both functional requirements and ensure performance.

Error 6: ignore Unicode/UTF-8 problems

In php programming, when dealing with non-ascii characters, some problems may occur. you should treat them with caution. otherwise, errors may occur everywhere. For example, strlen (name), if name contains non-ascii characters, the result is somewhat unexpected. Here are some suggestions to avoid such problems:

If you are not familiar with unicode and UTF-8, you should at least understand some basics. Read this article.
It is best to use the mb _ * function to process strings and avoid using the old string processing functions. Make sure that the "multibyte" extension of PHP is enabled.
It is best to use unicode encoding for databases and tables.
The jason_code () function converts non-ascii characters, but the serialize () function does not.
It is best to use UTF-8 without bom for php code source files.
I recommend an article here to introduce this problem in more detail: UTF-8 Primer for PHP and MySQL

Error 7: assume that $ _ POST always contains POST data.

$ _ POST in PHP does not always contain data submitted by form POST. Suppose we send a POST request to the server through the jQuery. ajax () method:

// js $.ajax({   url: 'http://my.site/some/path',   method: 'post',   data: JSON.stringify({a: 'a', b: 'b'}),   contentType: 'application/json'});

Note the contentType in the code: 'application/json'. we send data in json data format. On the server side, we only output the $ _ POST array:

// php var_dump($_POST);

You will be surprised to find that the result is as follows:

array(0) { }

Why is this? Where does 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. This is because of historical reasons. When PHP initially implemented $ _ POST, the most popular ones were the above two types. Therefore, although some types (such as application/json) are very popular, PHP still does not implement automatic processing.

Because POST is a global variable, change _ POST will be globally valid. Therefore, for requests whose Content-Type is application/json, we need to manually parse the json data and modify the $ _ POST variable.

// php $_POST = json_decode(file_get_contents('php://input'), true);

At this point, we will output the $ _ POST variable again, and we will get the expected output:

array(2) { ["a"]=> string(1) "a" ["b"]=> string(1) "b" }

Error 8: PHP supports character data types

Let's look at the following code and guess what will be output:

for ($c = 'a'; $c <= 'z'; $c++) {   echo $c . "\n"; }

If your answer is 'A' to 'Z', you will be surprised to find that your answer is wrong.

Yes, the above code will indeed output 'A' to 'Z', but in addition, it will output 'A' to 'yz '. Let's analyze why this is the result.

In PHP, the char data type does not exist, and only the string type exists. To understand this, perform the incremental operation on 'Z' and the result is 'A '. For strings that are relatively small, those who have learned C should all know that 'A' is smaller than 'z. This explains why the above output results exist.

If we want to output 'A' to 'Z', the following implementation is a good method:

for ($i = ord('a'); $i <= ord('z'); $i++) {   echo chr($i) . "\n"; }

Alternatively, it is OK:

$letters = range('a', 'z');  for ($i = 0; $i < count($letters); $i++) {   echo $letters[$i] . "\n"; }

Error 9: the encoding standard is ignored.

Although ignoring the encoding standard won't cause errors or bugs, it is very important to follow certain encoding standards.

No unified coding standards will cause many problems in your project. The most obvious thing is that your project code is not consistent. Worse, your code will be more difficult to debug, expand, and maintain. This means that your team's efficiency will be reduced, including a lot of meaningless work.

PHP developers are lucky. Because of the recommended PHP coding standard (PSR), it consists of the following five parts:

PSR-0: automatic loading standard
PSR-1: basic coding standards
PSR-2: Coding Style Guide
PSR-3: log interface standard
PSR-4: auto load
The SRS were initially created and followed by several major groups in the PHP community. Zend, Drupal, Symfony, Joomla and other platforms have contributed to this standard and follow this standard. Even PEAR wanted to set itself as a standard in the early years, but now it has joined the company's support team.

In some cases, it does not matter what encoding standards you use, as long as you use a encoding style and always stick to it. However, it is a good way to follow the PSR standard unless you have to set it for any special reason. Nowadays, a growing number of projects are using this service, and most PHP developers are using this service. Therefore, using this service will allow new members of your team to become familiar with this service more quickly, it is more comfortable to write code.

Error 10: the empty () function is incorrectly used.

Some PHP developers like to use empty () functions to make Boolean judgments on variables or expressions, but in some cases it may be confusing.

First, let's take a look at the Array and Array object ArrayObject in PHP. It seems that there is no difference. they are all the same. Is that true?

// 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 more complex, take a look at the following code:

// Prior to PHP 5.0: $array = []; var_dump(empty($array));    // outputs bool(false) $array = new ArrayObject(); var_dump(empty($array));    // outputs bool(false)

Unfortunately, this method is very popular. For example, in Zend Framework 2, Zend \ Db \ TableGateway calls the current () method in the TableGateway: select () result set to return data sets. Developers can easily step on this pitfall.

To avoid these problems, the last way to check whether an array is empty is to use the count () function:

// 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)

Here, by the way, because PHP considers the value 0 as a Boolean value of false, the count () function can be used directly in the condition judgment of the if condition statement to determine whether the array is null. In addition, the complexity of the count () function is O (1) for arrays, so using the count () function is a wise choice.

Let's take a look at a very dangerous example of using the empty () function. It is also dangerous to use the empty () function in the magic method _ get. Let's define two classes. Each class has a test attribute.

First, we define the Regular class, which has a test attribute:

class Regular {   public $test = 'value'; }

Then we define the Magic class and use the _ get () Magic method to access its test attribute:

class Magic {   private $values = ['test' => 'value'];    public function __get($key)   {     if (isset($this->values[$key])) {       return $this->values[$key];     }   } }

Okay. Now let's take a look at what happens when accessing the test attribute of each class:

$regular = new Regular(); var_dump($regular->test);  // outputs string(4) "value" $magic = new Magic(); var_dump($magic->test);   // outputs string(4) "value"

So far, it is still normal and we are not confused.

But what if I use the empty () function on the test attribute?

var_dump(empty($regular->test));  // outputs bool(false) var_dump(empty($magic->test));   // outputs bool(true)

Is the result quite unexpected?

Unfortunately, if a class uses the magic _ get () function to evaluate the class attribute value, there is no simple way to check whether the attribute value is null or does not exist. Outside the class scope, you can only check whether the 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 nonexistent attribute of the Regular class, we will get a message similar to the following Notice:

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 empty () functions, we must be careful when using them. Otherwise, the results may be unexpected, or even potentially misleading you.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.