# PHP tips/tricks/HOWTOs $Id: php.txt 1889 2008-08-18 21:46:56Z mjs $ ## HOWTO: Install ### OS X 10.5 (Leopard): Install, via port, libxml2, libxslt, libiconv, gd2, freetype, sqlite3. Once these are installed, configure with (adjust as appropriate if ports are not installed into /Ports): $ ./configure --prefix=$HOME/workspace/beebo/php \ --enable-fastcgi --with-xsl=/Ports --with-libxml-dir=/Ports \ --with-gd=/Ports --with-jpeg-dir=/Ports --with-pdo-sqlite=/Ports \ --without-sqlite --with-freetype-dir=/Ports --with-ttf=/Ports \ --enable-calendar --with-zlib=/Ports --with-iconv --with-openssl \ --enable-discard-path --with-mysql=$LOCAL --with-pdo-mysql=$LOCAL \ --enable-bcmath --with-curl `$LOCAL` is where the MySQL libraries and header files are installed. (e.g. `/usr`--you can't install MySQL into separate "binary" and "shared" directories.) You may want to add `--without-pear` if PEAR is installed separately and/or this build doesn't require it. You can also drop the `--without-sqlite` to use the bundled sqlite3. (In this case you also don't need to install sqlite3 via port.) The directory specified by `--prefix` determines where PHP is installed. The php and php-cgi binaries can be safely moved to other locations, however a few scripts have this location hardcoded, and won't work if the binaries are moved (`phpize`, `pear`). `--enable-discard-path` supposedly eliminates [a security problem](http://uk.php.net/manual/en/security.cgi-bin.shell.php). `--enable-bcmath` is needed for the Zend OpenId library. `--with-curl` provides `curl` support, which is used by the Zend HTTP Client (I think). (If compiling with some versions of OS X (10.5.0 and 10.5.4 seem to be affected, at least, replace `/usr/include/iconv.h` with MacPorts' version before configuring, to prevent `iconv` link errors.) $ make $ make install Link `php-cgi.dSYM` to `php-cgi`, and `php.dSYM` to `php` (DON'T FORGET THIS!) and `strip` the binaries. Then, in your `.htaccess` or `httpd.conf` file, arrange for `.php` files to be run through the `php-cgi` via FastCGI: AddHandler fastcgi-script fcgi Action application/php-fastcgi /bin/boom.fcgi AddType application/php-fastcgi .php Where `/bin/boom.fcgi` (`bin` is a subdirectory of document root, not the filesystem root!) is: PHP=/Users/mjs/local/php/bin/php-cgi INCLUDE_PATH=`pwd -L`/../../lib PHPINI=`pwd -L`/../../etc/php.ini exec $PHP -c $PHPINI -d include_path=$INCLUDE_PATH ### Ubuntu/Debian: Install the likely module suspects--libjpeg62-dev, libmysqlclient15-dev, libxslt1-dev, libcurl4-openssl-dev, libssl-dev, libsqlite3-dev, libgd2-noxpm, etc.--then configure with: $ ./configure --prefix=$HOME/beebo.org/php \ --enable-fastcgi --with-xsl=/usr --with-libxml-dir=/usr \ --with-gd --with-pdo-sqlite=/usr --without-sqlite \ --with-freetype-dir=/usr --with-ttf=/usr --enable-calendar \ --with-zlib --with-iconv --with-openssl --enable-discard-path \ --with-jpeg-dir=/usr/lib --with-png-dir=/usr/lib --with-mysql \ --with-pdo-mysql \ --enable-bcmath # for Zend OpenId library --with-curl # for curl support You may want to add `--without-pear` if PEAR is installed separately and/or this build doesn't require it. Note that for some reason the JPEG and PNG directory parameters specify the location of their respective *libraries*, not the root. (I don't know why either of these is necessary; for some reason the bundled GD doesn't come with JPEG and PNG libraries.) `strip` the binaries. ### SunOS: Install libxml2, libxslt, gd, libjpeg, libpng, zlib (configure zlib with --shared, otherwise you'll get link errors), then: $ lconfigure --with-apxs2=$LOCAL/bin/apxs \ --with-xsl=$LOCAL --with-libxml-dir=$LOCAL --with-gd=$LOCAL \ --with-jpeg-dir=$LOCAL --with-png-dir=$LOCAL --with-zlib-dir=$LOCAL \ --enable-debug --with-mysql=/usr/local/mysql --enable-calendar (Might also need to `ln -s $HOME/local/include` to `$LOCAL/include`--one of the dependencies couldn't find the include files from the architecture-independent directory.) ### Dreamhost: Install libxml2, libxslt, then: $ lconfigure --enable-fastcgi --with-xsl=$LOCAL \ --with-libxml-dir=$LOCAL --with-gd --with-ttf=/usr \ --with-freetype-dir=/usr --enable-calendar (Uses their gd, ttf, freetype, etc.) To install: $ make install Stripping the binary will save a lot of memory (optional): $ strip `which php` Note that this *doesn't* install the full command-line version (cli). To get that you need to $ make install-cli however the command-line version has the same filename--this installs a new "php" binary over the top of your old one. (? I think this changed around 5.2.3.) To get Dreamhost's Apache to run your php under FastCGI, you need these lines in your `.htaccess`: # Remove the default action for *.php files. RemoveHandler php # For some reason the following really does need to be called # dispatch.fcgi. The contents of dispatch.fcgi: # # #!/usr/bin/env bash # # exec ./php # # DOCUMENT_ROOT/cgi is symlinked to $LOCAL/bin. Dreamhost's config # seems to have already configured fastcgi-script for *.fcgi. Action php-fastcgi /cgi/dispatch.fcgi AddType php-fastcgi html AddType php-fastcgi php ### Windows/win32: 1. Get Apache running first. 2. Download the "[ZIP package](http://www.php.net/downloads.php)". 3. Add something like the following to httpd.conf: LoadModule php5_module "C:/server/php-5.2.4/php5apache2_2.dll" PHPIniDir "c:/server/php-5.2.4" 4. (Probably:) Copy php.ini-recommended to php.ini, and tweak as required. (If enabling modules, be sure to set extension_dir!) 5. Add your PHP directory to PATH, so that various supporting DLLs (e.g. libmysql.dll, libeay32.dll) can be loaded automatically. ([How to modify the PATH](http://php.net/manual/en/faq.installation.php#faq.installation.addtopath).) 1. (optional) Install the Zend debugger i. [Download site](http://downloads.zend.com/pdt/server-debugger/). (The "cygwin_nt-i386" version works fine with the regular Windows binaries.) i. Put the ZendDebugger.dll from the 5_2_x_comp (thread-safe) directory into your PHP extensions directory. (Probably C:\server\php-5.2.4\ext.) i. Modify `php.ini`, as described in the `README.txt`. i. Restart Apache. A phpinfo() call should mention the debugger. i. (After all this, couldn't get breakpoints working.) 1. (optional) Install the Xdebug debugger i. [Download site](http://www.xdebug.org/). i. Put the php_xdebug-2.0.0-5.2.2.dll into the PHP extensions directory. (Probably C:\server\php-5.2.4\ext.) i. Add the following to `php.ini`: zend_extension_ts=c:/server/php-5.2.4/ext/php_xdebug-2.0.0-5.2.2.dll xdebug.remote_enable = On xdebug.remote_host=10.171.99.124 i. Restart Apache. A phpinfo() call should mention the debugger. i. [More info](http://www.xdebug.org/docs/remote) ## HOWTO: Handle standard input and output ## TIP: Predefined variables ## TIP: Object oriented programing/inheritance ## TIP: String concatenation/string operators Use "." for concatenation; see [the PHP manual](http://php.net/language.operators.string) for more. ## HOWTO: Break out of a loop Use "[break](http://php.net/break)". ## TIP: Documentation of control structures Includes the alternative syntax. ## TIP: Regular expression example if (!preg_match("/^foo/", "some string")) { # ... } ## GOTCHA: ODBC This doesn't work in PHP: $sth = odbc_prepare($dbh, $sql); foreach (...) { $res = odbc_execute($sth, $arg); ... } (The prepared statement gets mashed somewhere--move the `odbc_prepare()` into the loop...) ## GOTCHA: MDB2's SQLite supports SQLite2, not SQLite3! True! You need to use PDO. ## HOWTO: Set the include path from the command line i.e. the equivalent of `PERL5LIB`, `PYTHONPATH`, `RUBYLIB`, etc.: $ php -d include_path=$PHPLIB ## HOWTO: Create a static constructor This doesn't seem to be possible in PHP; the best way is to do something like: Foo::$bar = "hullo, world"!; class Foo { public static $bar; } ## HOWTO: Static (class) variables class Foo { static $bar = null; function quux() { return self::$bar; } } ## HOWTO: Retrieve multiple values/process checkboxes, and multiple selects Give the variable a name that ends in "[]". If there are multiple keys with the same value, and the keys end in "[]", PHP creates an array of values for that value. i.e. if the HTML looks like and the user selects multiple beers, then gettype($_REQUEST["beer"]) == "array" See: ## HOWTO: "apply" the PHP way Use `call_user_func_array()`: call_user_func_array("foo", array(1, 2, 3)); Note that this can also be used to call a method: call_user_func_array(array($obj, $method), array(1, 2, 3)); ## HOWTO: Create MySQL-compatible date string format date("Y-m-d H:i:s") ## HOWTO: Convert XHTML entities to XML If you need to convert e.g. &rsquo; to &#8217; do something like: $table = array( 'Á' => 'Á', 'á' => 'á', ... ); $s = strtr($s, $table); The Wikipedia source contains an [entity conversion table](http://svn.wikimedia.org/svnroot/mediawiki/tags/REL1_9_3/phase3/includes/Sanitizer.php). ## TIP: Error control operator (@) `@` as a prefix [suppresses any warning or error that might otherwise be emitted](http://php.net/manual/en/language.operators.errorcontrol.php). Note that this works with both method calls and assignments: $res = @$doc->loadXML($string); $foo = @$GET_["foo"]; ([Other error handling functions](http://php.net/manual/en/ref.errorfunc.php#ini.track-errors).) ## HOWTO: Convert a UTF-8/Unicode string to XML/HTML entities I can't figure out how to do this! The closest I can get to is: $utf32 = iconv("UTF-8", "UTF-32BE", $utf8); foreach (unpack("N*", $utf32) as $c) { $xml .= sprintf("&#%d;", $c); } which produces a string where *every* character is expressed as a numeric entity. ## GOTCHA: `error_log()` quotes backlashes echo("\\hello\\") -> \hello\ error_log("\\hello\\") -> \\hello\\ ## WART: odbc_execute($sth, $a); If any of the element of `$a` begin and end with a single quote, the bit in between is interpreted as a filename, the contents of which are inserted. ## WART: Very strange arguments in php.ini: ; Define the probability that the 'garbage collection' process is started ; on every session initialization. ; The probability is calculated by using gc_probability/gc_divisor, ; e.g. 1/100 means there is a 1% chance that the GC process starts ; on each request. session.gc_probability = 1 session.gc_divisor = 1000 ## WART: Include is a "special language construct", which means that these two things are not equivalent: if ($condition) include $this; else include $that; and if ($condition) { include $this; } else { include $that; } ## WART: `array_filter()` and a`rray_map()` take their arguments in opposite order: array array_filter ( array input [, callback callback] ) array array_map ( callback callback, array arr1 [, array ...] ) ## WART: `imageepsbbox` returns an array of four values giving the bottom left and top right coordinates of a given string. `imagettfbbox` returns an array of eight values, giving the coordinates of all four corners of a given string. ## WART: `parse_str()` parses--guess what?--a query string. (Also, the inverse is `http_build_query()`.) More unfortunately-named pairs: * `htmlspecialchars()`/`htmlspecialchars_decode()` * `htmlentities()`/`html_entity_decode()` ## WART: The insanity of: isset($foo) empty($foo) is_null($foo) $foo (Could possibly add `array_key_exists()` and `defined()` to this list too.) Note also that `empty(trim($foo))` doesn't work--the argument to `empty()` needs to be a variable, not an expression. See also: ## WART: `include()` is relative to the original file, not the included file. See: You can `include()` relative to the included file with: include(dirname(__FILENAME__) . "/../$filename"); ## HOWTO: Interpolate complex array/object references Like this: echo "This works: {$obj->values[3]->name}"; See: ## FAQ: How to call parent constructor parent::__construct(...); [More info](http://php.net/language.oop5.decon). ## FAQ: How to use a custom statement class with PDO? [Comment describing how to extend PDOStatement](http://php.net/manual/en/ref.pdo.php#73568) (not tested). ## FAQ: Why does addition/subtraction not work! (Operator precedence) The operators "+", "-" and "." have the same precedence. This means that e.g. error_log("elapsed time = " . $t1 - $t0) doesn't do what you expect--it evaluates as error_log(("elapsed time = " . $t1) - $t0) (This is the same as Perl.) ([The full operator precedence table](http://php.net/manual/language.operators.html#language.operators.precedence). Note the Perl-like low precedence "and", "xor", "or".) ## GOTCHA: `parse_ini_file()` returns unexpected results "true", "on", "yes", "1" are converted to "1" (the number 1, as a string). "false", "off", "no" are converted to "" (the empty string). "0" is not converted, and is returned as "0" (the number 0, as a string). Because of this behaviour, and the way values are compared, when checking for a boolean value via `parse_ini_file()` (i.e. whether some setting is true or false), you should simply use `if ($value) { ... }` instead of trying to be clever with `if ($value == true) { ... }`, etc. ([PHP type comparison tables](http://php.net/types.comparisons).) ## GOTCHA: Array iterators pass by value, not reference $data = array(array(), array()); print_r($data); foreach ($data as $d) { $d[0] = "qqq"; } print_r($data); ## GOTCHA: Arithmetic comparison: `0.0 !== 0` `0.0 == 0`, but `0.0 !== 0`. For some reason this isn't listed in the [type comparison table](http://php.net/types.comparisons). ## HOWTO: Pretty-print an array `print_r()`, `var_export()` and the like output a line per key => value pair. If you want a few key => value pairs per line, try something like the following ($table is the original array): $a = array(); foreach ($table as $k => $v) { $a[] = sprintf("%11s => %9s,", "'{$k}'", "'{$v}'"); } for ($i = 0; $i < count($a); $i++) { echo $a[$i], " "; if (($i + 1) % 5 === 0) { echo "\n"; } } (Quick hack, doesn't handle ' in either the key or value, for example.) ## HOWTO: Extend PDO and use `__call()` within functions This doesn't work, and is [a bug](http://bugs.php.net/bug.php?id=43663), at least as of PHP 5.2.5. It can be fixed by applying [a patch](http://marc.info/?l=php-cvs&m=119903758912979&q=raw) and re-compiling. ## ERROR: Install fails with libgcrypt error This happens if you're building with XSL (`--with-xsl`) and libgcrypt isn't available. You need to install both libgcrypt and its dependency libgpg-error: * * (Or add `--without-xsl` to your `./configure` line.) ## ERROR: "Unable to load dynamic library XXX" (Followed by "The specified module could not be found", and where XXX actually exists.) PHP seems to produce this error fairly indiscriminately if the module can't be loaded for any reason at all, not just in the case where the apparently missing module doesn't exist on the filesystem. For example, it can happen if you neglect to include a dependency of the named module, such as if you load `php_pdo_oci.dll` without loading `php_pdo.dll`. It can also happen if an external dependency can't be found (database DLLs?) or the wrong version is being found (Apache DLLs? Windows DLLs?). (The PATH is searched for DLLs, but DLLs can come from other places as well, such as Apache's `bin` directory.) If you're having trouble with `php_mysql.dll`, make sure that `libmySQL.dll` can be found. If you're having trouble with `php_pdo_oci.dll`, make sure the Oracle client libraries can be found. If you're having trouble with `php_openssl.dll`, make sure `libeay32.dll` can be found, and that the wrong version (Apache's?) isn't being found. ## FAQ: What's the syntax for PDO DSNs? * [MySQL](http://php.net/manual/en/ref.pdo-mysql.connection.php) * [Oracle](http://php.net/manual/en/ref.pdo-oci.connection.php) * Other databases - load the [PDO documentation](http://php.net/pdo), select the driver you want, the select the "next" page using the navigation at the top or bottom. Username and password are specified as follows: $dbh = new PDO($dsn, $username, $password); ## HOWTO: `eval()` a string as if it were a PHP file i.e. a function like `include_string($string)` to parallel `include($filename)`. `eval($string)` evaluates `$string` as if it were PHP code. If you want to eval a string that looks like a PHP file (i.e. a string that contains ``), do: function include_string($string) { eval('?>' . $string . 'saveXML($node); ## FAQ: Where's the inverse of `strftime`? As far as I can tell, there isn't one. [`strtotime`](http://php.net/strtotime), [`date_parse`](http://php.net/date_parse) or--if you're using WordPress--`mysql2date` might do in some circumstances. ## HOWTO: Access raw POST data Use [`php://input`](http://php.net/wrappers.php): $data = file_get_contents("php://input"); The POSTed data is URL-encoded, so you may need to decode it first: $data = urldecode(file_get_contents("php://input")); ## HOWTO: Use `Zend_Mail` with Google Mail's SMTP server $config = array( 'auth' => 'login', 'username' => "XXXX@gmail.com", 'password' => "XXXX", 'ssl' => 'ssl', // not 'tls' 'port' => 465 ); $transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com', $config); $mail = new Zend_Mail(); $mail->setBodyText('This is the text of the mail.'); $mail->setFrom('sender@test.com', 'Some Sender'); $mail->addTo('mjs@beebo.org', 'Some Recipient'); $mail->setSubject('TestSubject'); $mail->send($transport); See [configuring other mail clients](http://mail.google.com/support/bin/answer.py?hl=en&answer=13287). ## HOWTO: Convert an object to an array Convert an object (i.e. `$user->name`) to an array (i.e. `$user["name"]`): $arr = (array) $obj; If you have nested objects you'll need to convert it manually: function object_to_array($obj) { if (!is_object($obj) && !is_array($obj)) { return $obj; } else { $a = array(); foreach ($obj as $k => $v) { $a[$k] = object_to_array($v); } return $a; } } ## HOWTO: Log reliably file_put_contents("/tmp/log", var_export($foo, true), FILE_APPEND | LOCK_EX); ## HOWTO: Dump stack reliably $a = array(); foreach (debug_backtrace() as $s) { $a[] = array( "file" => $s["file"], "link" => $s["line"] ); } rawlog($a);