1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-30 09:20:58 +01:00

Minor formatting changes for some documentation

Summary: Self explanatory.

Test Plan: Eyeball it.

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley, #blessed_reviewers

Subscribers: epriestley, Korvin

Differential Revision: https://secure.phabricator.com/D13278
This commit is contained in:
Joshua Spence 2015-06-15 07:32:28 +10:00
parent d9af48cc52
commit 0aab026f7e

View file

@ -4,11 +4,11 @@
This document discusses difficult traps and pitfalls in PHP, and how to avoid, This document discusses difficult traps and pitfalls in PHP, and how to avoid,
work around, or at least understand them. work around, or at least understand them.
= array_merge() in Incredibly Slow When Merging A List of Arrays = = `array_merge()` in Incredibly Slow When Merging A List of Arrays =
If you merge a list of arrays like this: If you merge a list of arrays like this:
COUNTEREXAMPLE COUNTEREXAMPLE, lang=php
$result = array(); $result = array();
foreach ($list_of_lists as $one_list) { foreach ($list_of_lists as $one_list) {
$result = array_merge($result, $one_list); $result = array_merge($result, $one_list);
@ -21,18 +21,19 @@ you iterate.
In a libphutil environment, you can use @{function@libphutil:array_mergev} In a libphutil environment, you can use @{function@libphutil:array_mergev}
instead. instead.
= var_export() Hates Baby Animals = = `var_export()` Hates Baby Animals =
If you try to var_export() an object that contains recursive references, your If you try to `var_export()` an object that contains recursive references, your
program will terminate. You have no chance to intercept or react to this or program will terminate. You have no chance to intercept or react to this or
otherwise stop it from happening. Avoid var_export() unless you are certain otherwise stop it from happening. Avoid `var_export()` unless you are certain
you have only simple data. You can use print_r() or var_dump() to display you have only simple data. You can use `print_r()` or `var_dump()` to display
complex variables safely. complex variables safely.
= isset(), empty() and Truthiness = = `isset()`, `empty()` and Truthiness =
A value is "truthy" if it evaluates to true in an `if` clause: A value is "truthy" if it evaluates to true in an `if` clause:
lang=php
$value = something(); $value = something();
if ($value) { if ($value) {
// Value is truthy. // Value is truthy.
@ -59,16 +60,16 @@ empty comments) is wrong in PHP:
This is wrong because it prevents users from making the comment "0". //THIS This is wrong because it prevents users from making the comment "0". //THIS
COMMENT IS TOTALLY AWESOME AND I MAKE IT ALL THE TIME SO YOU HAD BETTER NOT COMMENT IS TOTALLY AWESOME AND I MAKE IT ALL THE TIME SO YOU HAD BETTER NOT
BREAK IT!!!// A better test is probably strlen(). BREAK IT!!!// A better test is probably `strlen()`.
In addition to truth tests with `if`, PHP has two special truthiness operators In addition to truth tests with `if`, PHP has two special truthiness operators
which look like functions but aren't: empty() and isset(). These operators help which look like functions but aren't: `empty()` and `isset()`. These operators
deal with undeclared variables. help deal with undeclared variables.
In PHP, there are two major cases where you get undeclared variables -- either In PHP, there are two major cases where you get undeclared variables -- either
you directly use a variable without declaring it: you directly use a variable without declaring it:
COUNTEREXAMPLE COUNTEREXAMPLE, lang=php
function f() { function f() {
if ($not_declared) { if ($not_declared) {
// ... // ...
@ -84,200 +85,224 @@ you directly use a variable without declaring it:
} }
} }
When you do either of these, PHP issues a warning. Avoid these warnings by using When you do either of these, PHP issues a warning. Avoid these warnings by
empty() and isset() to do tests that are safe to apply to undeclared variables. using `empty()` and `isset()` to do tests that are safe to apply to undeclared
variables.
empty() evaluates truthiness exactly opposite of if(). isset() returns true for `empty()` evaluates truthiness exactly opposite of `if()`. `isset()` returns
everything except null. This is the truth table: `true` for everything except `null`. This is the truth table:
VALUE if() empty() isset() | Value | `if()` | `empty()` | `isset()` |
|-------|--------|-----------|-----------|
| `null` | `false` | `true` | `false` |
| `0` | `false` | `true` | `true` |
| `0.0` | `false` | `true` | `true` |
| `"0"` | `false` | `true` | `true` |
| `""` | `false` | `true` | `true` |
| `false` | `false` | `true` | `true` |
| `array()` | `false` | `true` | `true` |
| Everything else | `true` | `false` | `true` |
null false true false The value of these operators is that they accept undeclared variables and do
0 false true true not issue a warning. Specifically, if you try to do this you get a warning:
0.0 false true true
"0" false true true
"" false true true
false false true true
array() false true true
EVERYTHING ELSE true false true
The value of these operators is that they accept undeclared variables and do not ```lang=php, COUNTEREXAMPLE
issue a warning. Specifically, if you try to do this you get a warning:
COUNTEREXAMPLE
if ($not_previously_declared) { // PHP Notice: Undefined variable! if ($not_previously_declared) { // PHP Notice: Undefined variable!
// ... // ...
} }
```
But these are fine: But these are fine:
```lang=php
if (empty($not_previously_declared)) { // No notice, returns true. if (empty($not_previously_declared)) { // No notice, returns true.
// ... // ...
} }
if (isset($not_previously_declared)) { // No notice, returns false. if (isset($not_previously_declared)) { // No notice, returns false.
// ... // ...
} }
```
So, isset() really means is_declared_and_is_set_to_something_other_than_null(). So, `isset()` really means
empty() really means is_falsey_or_is_not_declared(). Thus: `is_declared_and_is_set_to_something_other_than_null()`. `empty()` really means
`is_falsey_or_is_not_declared()`. Thus:
- If a variable is known to exist, test falsiness with if (!$v), not empty(). - If a variable is known to exist, test falsiness with `if (!$v)`, not
In particular, test for empty arrays with if (!$array). There is no reason `empty()`. In particular, test for empty arrays with `if (!$array)`. There
to ever use empty() on a declared variable. is no reason to ever use `empty()` on a declared variable.
- When you use isset() on an array key, like isset($array['key']), it will - When you use `isset()` on an array key, like `isset($array['key'])`, it
evaluate to "false" if the key exists but has the value null! Test for index will evaluate to "false" if the key exists but has the value `null`! Test
existence with array_key_exists(). for index existence with `array_key_exists()`.
Put another way, use isset() if you want to type "if ($value !== null)" but are Put another way, use `isset()` if you want to type `if ($value !== null)` but
testing something that may not be declared. Use empty() if you want to type are testing something that may not be declared. Use `empty()` if you want to
"if (!$value)" but you are testing something that may not be declared. type `if (!$value)` but you are testing something that may not be declared.
= usort(), uksort(), and uasort() are Slow = = usort(), uksort(), and uasort() are Slow =
This family of functions is often extremely slow for large datasets. You should This family of functions is often extremely slow for large datasets. You should
avoid them if at all possible. Instead, build an array which contains surrogate avoid them if at all possible. Instead, build an array which contains surrogate
keys that are naturally sortable with a function that uses native comparison keys that are naturally sortable with a function that uses native comparison
(e.g., sort(), asort(), ksort(), or natcasesort()). Sort this array instead, and (e.g., `sort()`, `asort()`, `ksort()`, or `natcasesort()`). Sort this array
use it to reorder the original array. instead, and use it to reorder the original array.
In a libphutil environment, you can often do this easily with In a libphutil environment, you can often do this easily with
@{function@libphutil:isort} or @{function@libphutil:msort}. @{function@libphutil:isort} or @{function@libphutil:msort}.
= array_intersect() and array_diff() are Also Slow = = `array_intersect()` and `array_diff()` are Also Slow =
These functions are much slower for even moderately large inputs than These functions are much slower for even moderately large inputs than
array_intersect_key() and array_diff_key(), because they can not make the `array_intersect_key()` and `array_diff_key()`, because they can not make the
assumption that their inputs are unique scalars as the `key` varieties can. assumption that their inputs are unique scalars as the `key` varieties can.
Strongly prefer the `key` varieties. Strongly prefer the `key` varieties.
= array_uintersect() and array_udiff() are Definitely Slow Too = = `array_uintersect()` and `array_udiff()` are Definitely Slow Too =
These functions have the problems of both the `usort()` family and the These functions have the problems of both the `usort()` family and the
`array_diff()` family. Avoid them. `array_diff()` family. Avoid them.
= foreach() Does Not Create Scope = = `foreach()` Does Not Create Scope =
Variables survive outside of the scope of foreach(). More problematically, Variables survive outside of the scope of `foreach()`. More problematically,
references survive outside of the scope of foreach(). This code mutates references survive outside of the scope of `foreach()`. This code mutates
`$array` because the reference leaks from the first loop to the second: `$array` because the reference leaks from the first loop to the second:
COUNTEREXAMPLE ```lang=php, COUNTEREXAMPLE
$array = range(1, 3); $array = range(1, 3);
echo implode(',', $array); // Outputs '1,2,3' echo implode(',', $array); // Outputs '1,2,3'
foreach ($array as &$value) {} foreach ($array as &$value) {}
echo implode(',', $array); // Outputs '1,2,3' echo implode(',', $array); // Outputs '1,2,3'
foreach ($array as $value) {} foreach ($array as $value) {}
echo implode(',', $array); // Outputs '1,2,2' echo implode(',', $array); // Outputs '1,2,2'
```
The easiest way to avoid this is to avoid using foreach-by-reference. If you do The easiest way to avoid this is to avoid using foreach-by-reference. If you do
use it, unset the reference after the loop: use it, unset the reference after the loop:
```lang=php
foreach ($array as &$value) { foreach ($array as &$value) {
// ... // ...
} }
unset($value); unset($value);
```
= unserialize() is Incredibly Slow on Large Datasets = = `unserialize()` is Incredibly Slow on Large Datasets =
The performance of unserialize() is nonlinear in the number of zvals you The performance of `unserialize()` is nonlinear in the number of zvals you
unserialize, roughly O(N^2). unserialize, roughly `O(N^2)`.
zvals approximate time | zvals | Approximate time |
10000 5ms |-------|------------------|
100000 85ms | 10000 |5ms |
1000000 8,000ms | 100000 | 85ms |
10000000 72 billion years | 1000000 | 8,000ms |
| 10000000 | 72 billion years |
= `call_user_func()` Breaks References =
= call_user_func() Breaks References = If you use `call_use_func()` to invoke a function which takes parameters by
If you use call_use_func() to invoke a function which takes parameters by
reference, the variables you pass in will have their references broken and will reference, the variables you pass in will have their references broken and will
emerge unmodified. That is, if you have a function that takes references: emerge unmodified. That is, if you have a function that takes references:
```lang=php
function add_one(&$v) { function add_one(&$v) {
$v++; $v++;
} }
```
...and you call it with call_user_func(): ...and you call it with `call_user_func()`:
COUNTEREXAMPLE ```lang=php, COUNTEREXAMPLE
$x = 41; $x = 41;
call_user_func('add_one', $x); call_user_func('add_one', $x);
```
...`$x` will not be modified. The solution is to use call_user_func_array() ...`$x` will not be modified. The solution is to use `call_user_func_array()`
and wrap the reference in an array: and wrap the reference in an array:
```lang=php
$x = 41; $x = 41;
call_user_func_array( call_user_func_array(
'add_one', 'add_one',
array(&$x)); // Note '&$x'! array(&$x)); // Note '&$x'!
```
This will work as expected. This will work as expected.
= You Can't Throw From __toString() = = You Can't Throw From `__toString()` =
If you throw from __toString(), your program will terminate uselessly and you If you throw from `__toString()`, your program will terminate uselessly and you
won't get the exception. won't get the exception.
= An Object Can Have Any Scalar as a Property = = An Object Can Have Any Scalar as a Property =
Object properties are not limited to legal variable names: Object properties are not limited to legal variable names:
```lang=php
$property = '!@#$%^&*()'; $property = '!@#$%^&*()';
$obj->$property = 'zebra'; $obj->$property = 'zebra';
echo $obj->$property; // Outputs 'zebra'. echo $obj->$property; // Outputs 'zebra'.
```
So, don't make assumptions about property names. So, don't make assumptions about property names.
= There is an (object) Cast = = There is an `(object)` Cast =
You can cast a dictionary into an object. You can cast a dictionary into an object.
```lang=php
$obj = (object)array('flavor' => 'coconut'); $obj = (object)array('flavor' => 'coconut');
echo $obj->flavor; // Outputs 'coconut'. echo $obj->flavor; // Outputs 'coconut'.
echo get_class($obj); // Outputs 'stdClass'. echo get_class($obj); // Outputs 'stdClass'.
```
This is occasionally useful, mostly to force an object to become a Javascript This is occasionally useful, mostly to force an object to become a Javascript
dictionary (vs a list) when passed to json_encode(). dictionary (vs a list) when passed to `json_encode()`.
= Invoking "new" With an Argument Vector is Really Hard = = Invoking `new` With an Argument Vector is Really Hard =
If you have some `$class_name` and some `$argv` of constructor If you have some `$class_name` and some `$argv` of constructor arguments
arguments and you want to do this: and you want to do this:
```lang=php
new $class_name($argv[0], $argv[1], ...); new $class_name($argv[0], $argv[1], ...);
```
...you'll probably invent a very interesting, very novel solution that is very ...you'll probably invent a very interesting, very novel solution that is very
wrong. In a libphutil environment, solve this problem with wrong. In a libphutil environment, solve this problem with
@{function@libphutil:newv}. Elsewhere, copy newv()'s implementation. @{function@libphutil:newv}. Elsewhere, copy `newv()`'s implementation.
= Equality is not Transitive = = Equality is not Transitive =
This isn't terribly surprising since equality isn't transitive in a lot of This isn't terribly surprising since equality isn't transitive in a lot of
languages, but the == operator is not transitive: languages, but the `==` operator is not transitive:
```lang=php
$a = ''; $b = 0; $c = '0a'; $a = ''; $b = 0; $c = '0a';
$a == $b; // true $a == $b; // true
$b == $c; // true $b == $c; // true
$c == $a; // false! $c == $a; // false!
```
When either operand is an integer, the other operand is cast to an integer When either operand is an integer, the other operand is cast to an integer
before comparison. Avoid this and similar pitfalls by using the === operator, before comparison. Avoid this and similar pitfalls by using the `===` operator,
which is transitive. which is transitive.
= All 676 Letters in the Alphabet = = All 676 Letters in the Alphabet =
This doesn't do what you'd expect it to do in C: This doesn't do what you'd expect it to do in C:
```lang=php
for ($c = 'a'; $c <= 'z'; $c++) { for ($c = 'a'; $c <= 'z'; $c++) {
// ... // ...
} }
```
This is because the successor to 'z' is 'aa', which is "less than" 'z'. The This is because the successor to `z` is `aa`, which is "less than" `z`.
loop will run for ~700 iterations until it reaches 'zz' and terminates. That is, The loop will run for ~700 iterations until it reaches `zz` and terminates.
`$c` will take on these values: That is, `$c` will take on these values:
```
a a
b b
... ...
@ -293,9 +318,12 @@ loop will run for ~700 iterations until it reaches 'zz' and terminates. That is,
zx zx
zy zy
zz // loop now terminates because 'zz' > 'z' zz // loop now terminates because 'zz' > 'z'
```
Instead, use this loop: Instead, use this loop:
```lang=php
foreach (range('a', 'z') as $c) { foreach (range('a', 'z') as $c) {
// ... // ...
} }
```