# -*- php -*- # The examples are taken from the Perl Cookbook # By Tom Christiansen & Nathan Torkington # see http://www.oreilly.com/catalog/cookbook for more # @@PLEAC@@_NAME # @@SKIP@@ PHP # @@PLEAC@@_WEB # @@SKIP@@ http://php.net/ # @@PLEAC@@_1.0 #----------------------------- $string = '\n'; # two characters, \ and an n $string = 'Jon \'Maddog\' Orwant'; # literal single quotes $string = 'Jon "Maddog" Orwant'; # literal double quotes #----------------------------- $string = "\n"; # a "newline" character $string = "Jon \"Maddog\" Orwant"; # literal double quotes $string = "Jon 'Maddog' Orwant"; # literal single quotes #----------------------------- $a = "This is a multiline here document"; $a = << 0; $offset++) { // $matches[1] has charcter, ord($matches[1]) its number } #----------------------------- $seen = array(); $string = "an apple a day"; foreach (str_split($string) as $char) { $seen[$char] = 1; } $keys = array_keys($seen); sort($keys); print "unique chars are: " . implode('', $keys)) . "\n"; unique chars are: adelnpy #----------------------------- $seen = array(); $string = "an apple a day"; for ($offset = 0; preg_match('/(.)/', $string, $matches, 0, $offset) > 0; $offset++) { $seen[$matches[1]] = 1; } $keys = array_keys($seen); sort($keys); print "unique chars are: " . implode('', $keys) . "\n"; unique chars are: adelnpy #----------------------------- $sum = 0; foreach (unpack("C*", $string) as $byteval) { $sum += $byteval; } print "sum is $sum\n"; // prints "1248" if $string was "an apple a day" #----------------------------- $sum = array_sum(unpack("C*", $string)); #----------------------------- // sum - compute 16-bit checksum of all input files $handle = @fopen($argv[1], 'r'); $checksum = 0; while (!feof($handle)) { $checksum += (array_sum(unpack("C*", fgets($handle)))); } $checksum %= pow(2,16) - 1; print "$checksum\n"; # @@INCLUDE@@ include/php/slowcat.php #----------------------------- # @@PLEAC@@_1.6 #----------------------------- $revchars = strrev($string); #----------------------------- $revwords = implode(" ", array_reverse(explode(" ", $string))); #----------------------------- // reverse word order $string = 'Yoda said, "can you see this?"'; $allwords = explode(" ", $string); $revwords = implode(" ", array_reverse($allwords)); print $revwords . "\n"; this?" see you "can said, Yoda #----------------------------- $revwords = implode(" ", array_reverse(explode(" ", $string))); #----------------------------- $revwords = implode(" ", array_reverse(preg_split("/(\s+)/", $string))); #----------------------------- $word = "reviver"; $is_palindrome = ($word === strrev($word)); #----------------------------- // quite a one-liner since "php" does not have a -n switch % php -r 'while (!feof(STDIN)) { $word = rtrim(fgets(STDIN)); if ($word == strrev($word) && strlen($word) > 5) print $word; }' < /usr/dict/words #----------------------------- # @@PLEAC@@_1.8 #----------------------------- $text = preg_replace('/\$(\w+)/e', '$$1', $text); #----------------------------- list($rows, $cols) = Array(24, 80); $text = 'I am $rows high and $cols long'; $text = preg_replace('/\$(\w+)/e', '$$1', $text); print $text; #----------------------------- $text = "I am 17 years old"; $text = preg_replace('/(\d+)/e', '2*$1', $text); #----------------------------- # expand variables in $text, but put an error message in # if the variable isn't defined $text = preg_replace('/\$(\w+)/e','isset($$1)?$$1:\'[NO VARIABLE: $$1]\'', $text); #----------------------------- // As PHP arrays are used as hashes too, separation of section 4 // and section 5 makes little sense. # @@PLEAC@@_1.9 #----------------------------- $big = strtoupper($little); $little = strtolower($big); // PHP does not have the\L and\U string escapes. #----------------------------- $big = ucfirst($little); $little = strtolower(substr($big, 0, 1)) . substr($big, 1); #----------------------------- $beast = "dromedary"; // capitalize various parts of $beast $capit = ucfirst($beast); // Dromedar // PHP does not have the\L and\U string escapes. $capall = strtoupper($beast); // DROMEDAR // PHP does not have the\L and\U string escapes. $caprest = strtolower(substr($beast, 0, 1)) . substr(strtoupper($beast), 1); // dROMEDAR // PHP does not have the\L and\U string escapes. #----------------------------- // titlecase each word's first character, lowercase the rest $text = "thIS is a loNG liNE"; $text = ucwords(strtolower($text)); print $text; This Is A Long Line #----------------------------- if (strtoupper($a) == strtoupper($b)) { // or strcasecmp($a, $b) == 0 print "a and b are the same\n"; } #----------------------------- # @@INCLUDE@@ include/php/randcap.php // % php randcap.php < genesis | head -9 #----------------------------- # @@PLEAC@@_1.10 #----------------------------- echo $var1 . func() . $var2; // scalar only #----------------------------- // PHP can only handle variable expression without operators $answer = "STRING ${[ VAR EXPR ]} MORE STRING"; #----------------------------- $phrase = "I have " . ($n + 1) . " guanacos."; // PHP cannot handle the complex exression: ${\($n + 1)} #----------------------------- // Rest of Discussion is not applicable to PHP #----------------------------- // Interpolating functions not available in PHP #----------------------------- # @@PLEAC@@_1.11 # @@INCOMPLETE@@ # @@INCOMPLETE@@ # @@PLEAC@@_1.12 #----------------------------- $output = wordwrap($str, $width, $break, $cut); #----------------------------- # @@INCLUDE@@ include/php/wrapdemo.php #----------------------------- // merge multiple lines into one, then wrap one long line print wordwrap(str_replace("\n", " ", file_get_contents('php://stdin'))); #----------------------------- while(!feof(STDIN)) { print wordwrap(str_replace("\n", " ", stream_get_line(STDIN, 0, "\n\n"))); print "\n\n"; } #----------------------------- # @@PLEAC@@_1.13 #----------------------------- //backslash $var = preg_replace('/([CHARLIST])/', '\\\$1', $var); // double $var = preg_replace('/([CHARLIST])/', '$1$1', $var); #----------------------------- $var = preg_replace('/%/', '%%', $var); #----------------------------- $string = 'Mom said, "Don\'t do that."'; $string = preg_replace('/([\'"])/', '\\\$1', $string); // in PHP you can also use the addslashes() function #----------------------------- $string = 'Mom said, "Don\'t do that."'; $string = preg_replace('/([\'"])/', '$1$1', $string); #----------------------------- $string = preg_replace('/([^A-Z])/', '\\\$1', $string); #----------------------------- // PHP does not have the \Q and \E string metacharacters $string = "this is\\ a\\ test\\!"; // PHP's quotemeta() function is not the same as perl's quotemeta() function $string = preg_replace('/(\W)/', '\\\$1', 'is a test!'); #----------------------------- # @@PLEAC@@_1.14 #----------------------------- $string = trim($string); #----------------------------- // print what's typed, but surrounded by > < symbols while (!feof(STDIN)) { print ">" . substr(fgets(STDIN), 0, -1) . "<\n"; } #----------------------------- $string = preg_replace('/\s+/', ' ', $string); // finally, collapse middle #----------------------------- $string = trim($string); $string = preg_replace('/\s+/', ' ', $string); #----------------------------- // 1. trim leading and trailing white space // 2. collapse internal whitespace to single space each function sub_trim($string) { $string = trim($string); $string = preg_replace('/\s+/', ' ', $string); return $string; } #----------------------------- # @@PLEAC@@_1.15 # @@INCOMPLETE@@ # @@INCOMPLETE@@ # @@PLEAC@@_1.16 #----------------------------- $code = soundex($string); #----------------------------- $phoned_words = metaphone("Schwern"); #----------------------------- // substitution function for getpwent(): // returns an array of user entries, // each entry contains the username and the full name function getpwent() { $pwents = array(); $handle = fopen("passwd", "r"); while (!feof($handle)) { $line = fgets($handle); if (preg_match("/^#/", $line)) continue; $cols = explode(":", $line); $pwents[$cols[0]] = $cols[4]; } return $pwents; } print "Lookup user: "; $user = rtrim(fgets(STDIN)); if (empty($user)) exit; $name_code = soundex($user); $pwents = getpwent(); foreach($pwents as $username => $fullname) { preg_match("/(\w+)[^,]*\b(\w+)/", $fullname, $matches); list(, $firstname, $lastname) = $matches; if ($name_code == soundex($username) || $name_code == soundex($lastname) || $name_code == soundex($firstname)) { printf("%s: %s %s\n", $username, $firstname, $lastname); } } #----------------------------- # @@PLEAC@@_1.17 #----------------------------- # @@INCLUDE@@ include/php/fixstyle.php #----------------------------- # @@INCLUDE@@ include/php/fixstyle2.php #----------------------------- // very fast, but whitespace collapse while (!feof($input)) { $i = 0; preg_match("/^(\s*)(.*)/", fgets($input), $matches); // emit leading whitespace fwrite($output, $matches[1]); foreach (preg_split("/(\s+)/", $matches[2]) as $token) { // preserve trailing whitespace fwrite($output, (array_key_exists($token, $config) ? $config[$token] : $token) . " "); } fwrite($output, "\n"); } #----------------------------- // @@PLEAC@@_2.0 // As is the case under so many other languages floating point use under PHP is fraught // with dangers. Although the basic techniques shown below are valid, please refer to // the official PHP documentation for known issues, bugs, and alternate approaches // @@PLEAC@@_2.1 // Two basic approaches to numeric validation: // * Built-in functions like 'is_numeric', 'is_int', 'is_float' etc // * Regexes, as shown below $s = '12.345'; preg_match('/\D/', $s) && die("has nondigits\n"); preg_match('/^\d+$/', $s) || die("not a natural number\n"); preg_match('/^-?\d+$/', $s) || die("not an integer\n"); preg_match('/^[+-]?\d+$/', $s) || die("not an integer\n"); preg_match('/^-?\d+\.?\d*$/', $s) || die("not a decimal\n"); preg_match('/^-?(?:\d+(?:\.\d*)?|\.\d+)$/', $s) || die("not a decimal\n"); preg_match('/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/', $s) || die("not a C float\n"); // ---------------------------- function getnum($s) { sscanf($s, "%D", $number); return isset($number) ? $number : 0; } echo getnum(123) . "\n"; // ok echo getnum(0xff) . "\n"; // .. echo getnum(044) . "\n"; // .. echo getnum('x') . "\n"; // fail // @@PLEAC@@_2.2 // In PHP floating point comparisions are 'safe' [meaning the '==' comparison operator // can be used] as long as the value consists of 14 digits or less [total digits, either // side of the decimal point e.g. xxxxxxx.xxxxxxx, xxxxxxxxxxxxxx., .xxxxxxxxxxxxxx]. If // values with more digits must be compared, then: // // * Represent as strings, and take care to avoid any implicit conversions e.g. don't pass // a float as a float to a function and expect all digits to be retained - they won't be - // then use 'strcmp' to compare the strings // // * Avoid float use; perhaps use arbitrary precision arithmetic. In this case, the // 'bccomp' function is relevant // Will work as long as each floating point value is 14 digits or less if ($float_1 == $float_2) { ; // ... } // Compare as strings $cmp = strcmp('123456789.123456789123456789', '123456789.123456789123456788'); // Use 'bccomp' $precision = 5; // Number of significant comparison digits after decimal point if (bccomp('1.111117', '1.111116', $precision)) { ; // ... } $precision = 6; if (bccomp('1.111117', '1.111116', $precision)) { ; // ... } // ---------------------------- $wage = 536; $week = $wage * 40; printf("One week's wage is: $%.2f\n", $week / 100); // @@PLEAC@@_2.3 // Preferred approach $rounded = round($unrounded, $precision); // Possible alternate approach $format = '%[width].[prec]f'; $rounded = sprintf($format, $unrounded); // ------------ $a = 0.255; $b = round($a, 2); echo "Unrounded: {$a}\nRounded: {$b}\n"; $a = 0.255; $b = sprintf('%.2f', $a); echo "Unrounded: {$a}\nRounded: {$b}\n"; $a = 0.255; printf("Unrounded: %.f\nRounded: %.2f\n", $a, $a); // ---------------------------- echo "number\tint\tfloor\tceil\n"; foreach(array(3.3, 3.5, 3.7, -3.3) as $number) { printf("%.1f\t%.1f\t%.1f\t%.1f\n", $number, (int) $number, floor($number), ceil($number)); } // @@PLEAC@@_2.4 // PHP offers the 'bindec' and 'decbin' functions to converting between binary and decimal $num = bindec('0110110'); $binstr = decbin(54); // @@PLEAC@@_2.5 foreach (range($X, $Y) as $i) { ; // ... } foreach (range($X, $Y, 7) as $i) { ; // ... } for ($i = $X; $i <= $Y; $i++) { ; // ... } for ($i = $X; $i <= $Y; $i += 7) { ; // ... } // ---------------------------- echo 'Infancy is:'; foreach(range(0, 2) as $i) echo " {$i}\n"; echo 'Toddling is:'; foreach(range(3, 4) as $i) echo " {$i}\n"; echo 'Childhood is:'; foreach(range(5, 12) as $i) echo " {$i}\n"; // @@PLEAC@@_2.6 // PHP offers no native support for Roman Numerals. However, a 'Numbers_Roman' class // is available for download from PEAR: [http://pear.php.net/package/Numbers_Roman]. // Note the following 'include' directives are required: // // include_once('Numbers/Roman.php'); $roman = Numbers_Roman::toNumeral($arabic); $arabic = Numbers_Roman::toNumber($roman); // ---------------------------- $roman_fifteen = Numbers_Roman::toNumeral(15); $arabic_fifteen = Numbers_Roman::toNumber($roman_fifteen); printf("Roman for fifteen is: %s\n", $roman_fifteen); printf("Arabic for fifteen is: %d\n", $arabic_fifteen); // @@PLEAC@@_2.7 // Techniques used here simply mirror Perl examples, and are not an endorsement // of any particular RNG technique // In PHP do this ... $random = rand($lowerbound, $upperbound); $random = rand($x, $y); // ---------------------------- function make_password($chars, $reqlen) { $len = strlen($chars); for ($i = 0; $i < $reqlen; $i++) $password .= substr($chars, rand(0, $len), 1); return $password; } $chars = 'ABCDEfghijKLMNOpqrstUVWXYz'; $reqlen = 8; $password = make_password($chars, $reqlen); // @@PLEAC@@_2.8 // PHP sports a large number of C Standard Library routines including the 'srand' // function, used to re-seed the RNG used with calls to the 'rand' function. Thus, // as per Perl example: while (TRUE) { $seed = (int) fgets(STDIN); if (!empty($seed)) break; } srand($seed); // @@PLEAC@@_2.9 // The above is considered - for many reasons - a poor way of seeding the RNG. PHP // also offers alternate versions of the functions, 'mt_srand' and 'mt_rand', // which are described as faster, and more 'random', though key to obtaining a // more 'random' distribution of generated numbers seems to be through using // a combination of a previously saved random value in combination with an // unrepeatable value [like the current time in microseconds] that is multiplied // by a large prime number, or perhaps as part of a hash [examples available in // PHP documentation for 'srand' and 'mt_srand'] mt_srand($saved_random_value + microtime() * 1000003); // or mt_srand(($saved_random_value + hexdec(substr(md5(microtime()), -8))) & 0x7fffffff); // Use of 'mt_rand' together with an appropriate seeding approach should help better // approximate the generation of a 'truly random value' $truly_random_value = mt_rand(); // @@PLEAC@@_2.10 function random() { return (float) rand() / (float) getrandmax(); } function gaussian_rand() { $u1 = 0.0; $u2 = 0.0; $g1 = 0.0; $g2 = 0.0; $w = 0.0; do { $u1 = 2.0 * random() - 1.0; $u2 = 2.0 * random() - 1.0; $w = $u1 * $u1 + $u2 * $u2; } while ($w > 1.0); $w = sqrt((-2.0 * log($w)) / $w); $g2 = $u1 * $w; $g1 = $u2 * $w; return $g1; } // ------------ $mean = 25.0; $sdev = 2.0; $salary = gaussian_rand() * $mean + $sdev; printf("You have been hired at: %.2f\n", $salary); // @@PLEAC@@_2.11 // 'deg2rad' and 'rad2deg' are actually PHP built-ins, but here is how you might implement / them if needed function deg2rad_($deg) { return ($deg / 180.0) * M_PI; } function rad2deg_($rad) { return ($rad / M_PI) * 180.0; } // ------------ printf("%f\n", deg2rad_(180.0)); printf("%f\n", deg2rad(180.0)); // ---------------------------- function degree_sin($deg) { return sin(deg2rad($deg)); } // ------------ $rad = deg2rad(380.0); printf("%f\n", sin($rad)); printf("%f\n", degree_sin(380.0)); // @@PLEAC@@_2.12 function my_tan($theta) { return sin($theta) / cos($theta); } // ------------ $theta = 3.7; printf("%f\n", my_tan($theta)); printf("%f\n", tan($theta)); // @@PLEAC@@_2.13 $value = 100.0; $log_e = log($value); $log_10 = log10($value); // ---------------------------- function log_base($base, $value) { return log($value) / log($base); } // ------------ $answer = log_base(10.0, 10000.0); printf("log(10, 10,000) = %f\n", $answer); // @@PLEAC@@_2.14 // PHP offers no native support for matrices. However, a 'Math_Matrix' class // is available for download from PEAR: [http://pear.php.net/package/Math_Matrix]. // Note the following 'include' directives are required: // // include_once('Math/Matrix.php'); $a = new Math_Matrix(array(array(3, 2, 3), array(5, 9, 8))); $b = new Math_Matrix(array(array(4, 7), array(9, 3), array(8, 1))); echo $a->toString() . "\n"; echo $b->toString() . "\n"; // NOTE: When I installed this package I had to rename the 'clone' method else // it would not load, so I chose to rename it to 'clone_', and this usage is // shown below. This bug may well be fixed by the time you obtain this package $c = $a->clone_(); $c->multiply($b); echo $c->toString() . "\n"; // @@PLEAC@@_2.15 // PHP offers no native support for complex numbers. However, a 'Math_Complex' class // is available for download from PEAR: [http://pear.php.net/package/Math_Complex]. // Note the following 'include' directives are required: // // include_once('Math/Complex.php'); // include_once('Math/TrigOp.php'); // include_once('Math/ComplexOp.php'); $a = new Math_Complex(3, 5); $b = new Math_Complex(2, -2); $c = Math_ComplexOp::mult($a, $b); echo $c->toString() . "\n"; // ---------------------------- $d = new Math_Complex(3, 4); $r = Math_ComplexOp::sqrt($d); echo $r->toString() . "\n"; // @@PLEAC@@_2.16 // Like C, PHP supports decimal-alternate notations. Thus, for example, the integer // value, 867, is expressable in literal form as: // // Hexadecimal -> 0x363 // Octal -> 01543 // // For effecting such conversions using strings there is 'sprintf' and 'sscanf'. $dec = 867; $hex = sprintf('%x', $dec); $oct = sprintf('%o', $dec); // ------------ $dec = 0; $hex = '363'; sscanf($hex, '%x', $dec); // ------------ $dec = 0; $oct = '1543'; sscanf($oct, '%o', $dec); // ---------------------------- $number = 0; printf('Gimme a number in decimal, octal, or hex: '); sscanf(fgets(STDIN), '%D', $number); printf("%d %x %o\n", $number, $number, $number); // @@PLEAC@@_2.17 // PHP offers the 'number_format' built-in function to, among many other format tasks, // commify numbers. Perl-compatible [as well as extended] regexes are also available function commify_series($s) { return number_format($s, 0, '', ','); } // ------------ $hits = 3456789; printf("Your website received %s accesses last month\n", commify_series($hits)); // ---------------------------- function commify($s) { return strrev(preg_replace('/(\d\d\d)(?=\d)(?!\d*\.)/', '${1},', strrev($s))); } // ------------ $hits = 3456789; echo commify(sprintf("Your website received %d accesses last month\n", $hits)); // @@PLEAC@@_2.18 function pluralise($value, $root, $singular='' , $plural='s') { return $root . (($value > 1) ? $plural : $singular); } // ------------ $duration = 1; printf("It took %d %s\n", $duration, pluralise($duration, 'hour')); printf("%d %s %s enough.\n", $duration, pluralise($duration, 'hour'), pluralise($duration, '', 'is', 'are')); $duration = 5; printf("It took %d %s\n", $duration, pluralise($duration, 'hour')); printf("%d %s %s enough.\n", $duration, pluralise($duration, 'hour'), pluralise($duration, '', 'is', 'are')); // ---------------------------- function plural($singular) { $s2p = array('/ss$/' => 'sses', '/([psc]h)$/' => '${1}es', '/z$/' => 'zes', '/ff$/' => 'ffs', '/f$/' => 'ves', '/ey$/' => 'eys', '/y$/' => 'ies', '/ix$/' => 'ices', '/([sx])$/' => '$1es', '$' => 's'); foreach($s2p as $s => $p) { if (preg_match($s, $singular)) return preg_replace($s, $p, $singular); } } // ------------ foreach(array('mess', 'index', 'leaf', 'puppy') as $word) { printf("%6s -> %s\n", $word, plural($word)); } // @@PLEAC@@_2.19 // @@INCOMPLETE@@ // @@INCOMPLETE@@ // @@PLEAC@@_3.0 // PHP's date / time suport is quite extensive, and appears grouped into three areas of // functionality: // // * UNIX / C Library [libc]-based routines, which include [among others]: // - localtime, gmtime // - strftime, strptime, mktime // - time, getdate, gettimeofday, // // * PHP 'native' functions, those date / time routines released in earlier versions, // and which otherwise provide 'convenience' functionality; these include: // - date // - strtotime // // * 'DateTime' class-based. This facility appears [according to the PHP documentation] // to be extremely new / experimental, so whilst usage examples will be provided, they // should not be taken to be 'official' examples, and obviously, subject to change. // My own impression is that this facility is currently only partially implemented, // so there is limited use for these functions. The functions included in this group // are some of the 'date_'-prefixed functions; they are, however, not used standalone, // but as methods in conjunction with an object. Typical usage: // // $today = new DateTime(); // actually calls: date_create($today, ...); // echo $today->format('U') . "\n"; // actually calls: date_format($today, ...); // // Also worth mentioning is the PEAR [PHP Extension and Repository] package, 'Calendar', // which offers a rich set of date / time manipulation facilities. However, since it is // not currently shipped with PHP, no examples appear // Helper functions for performing date arithmetic function dateOffset() { static $tbl = array('sec' => 1, 'min' => 60, 'hou' => 3600, 'day' => 86400, 'wee' => 604800); $delta = 0; foreach (func_get_args() as $arg) { $kv = explode('=', $arg); $delta += $kv[1] * $tbl[strtolower(substr($kv[0], 0, 3))]; } return $delta; } function dateInterval($intvltype, $timevalue) { static $tbl = array('sec' => 1, 'min' => 60, 'hou' => 3600, 'day' => 86400, 'wee' => 604800); return (int) round($timevalue / $tbl[strtolower(substr($intvltype, 0, 3))]); } // ---------------------------- // Extract indexed array from 'getdate' $today = getdate(); printf("Today is day %d of the current year\n", $today['yday']); // Extract indexed, and associative arrays, respectively, from 'localtime' $today = localtime(); printf("Today is day %d of the current year\n", $today[7]); $today = localtime(time(), TRUE); printf("Today is day %d of the current year\n", $today['tm_yday']); // @@PLEAC@@_3.1 define(SEP, '-'); // ------------ $today = getdate(); $day = $today['mday']; $month = $today['mon']; $year = $today['year']; // Either do this to use interpolation: $sep = SEP; echo "Current date is: {$year}{$sep}{$month}{$sep}{$day}\n"; // or simply concatenate: echo 'Current date is: ' . $year . SEP . $month . SEP . $day . "\n"; // ------------ $today = localtime(time(), TRUE); $day = $today['tm_mday']; $month = $today['tm_mon'] + 1; $year = $today['tm_year'] + 1900; printf("Current date is: %4d%s%2d%s%2d\n", $year, SEP, $month, SEP, $day); // ------------ $format = 'Y' . SEP . 'n' . SEP . 'd'; $today = date($format); echo "Current date is: {$today}\n"; // ------------ $sep = SEP; $today = strftime("%Y$sep%m$sep%d"); echo "Current date is: {$today}\n"; // @@PLEAC@@_3.2 $timestamp = mktime($hour, $min, $sec, $month, $day, $year); $timestamp = gmmktime($hour, $min, $sec, $month, $day, $year); // @@PLEAC@@_3.3 $dmyhms = getdate(); // timestamp: current date / time $dmyhms = getdate($timestamp); // timestamp: arbitrary $day = $dmyhms['mday']; $month = $dmyhms['mon']; $year = $dmyhms['year']; $hours = $dmyhms['hours']; $minutes = $dmyhms['minutes']; $seconds = $dmyhms['seconds']; // @@PLEAC@@_3.4 // Date arithmetic is probably most easily performed using timestamps [i.e. *NIX Epoch // Seconds]. Dates - in whatever form - are converted to timestamps, these are // arithmetically manipulated, and the result converted to whatever form required. // Note: use 'mktime' to create timestamps properly adjusted for daylight saving; whilst // 'strtotime' is more convenient to use, it does not, AFAIK, include this adjustment $when = $now + $difference; $then = $now - $difference; // ------------ $now = mktime(0, 0, 0, 8, 6, 2003); $diff1 = dateOffset('day=1'); $diff2 = dateOffset('weeks=2'); echo 'Today is: ' . date('Y-m-d', $now) . "\n"; echo 'One day in the future is: ' . date('Y-m-d', $now + $diff1) . "\n"; echo 'Two weeks in the past is: ' . date('Y-m-d', $now - $diff2) . "\n"; // ---------------------------- // Date arithmetic performed using a custom function, 'dateOffset'. Internally, offset may // be computed in one of several ways: // * Direct timestamp manipulation - fastest, but no daylight saving adjustment // * Via 'date' built-in function - slower [?], needs a base time from which to // compute values, but has daylight saving adjustment // * Via 'strtotime' built-in function - as for 'date' // * Via 'DateTime' class // // Approach used here is to utilise direct timestamp manipulation in 'dateOffset' [it's // performance can also be improved by replacing $tbl with a global definition etc], // and to illustrate how the other approaches might be used // 1. 'dateOffset' $birthtime = mktime(3, 45, 50, 1, 18, 1973); $interval = dateOffset('day=55', 'hours=2', 'min=17', 'sec=5'); $then = $birthtime + $interval; printf("Birthtime is: %s\nthen is: %s\n", date(DATE_RFC1123, $birthtime), date(DATE_RFC1123, $then)); // ------------ // 2. 'date' // Base values, and offsets, respectively $hr = 3; $min = 45; $sec = 50; $mon = 1; $day = 18; $year = 1973; $yroff = 0; $monoff = 0; $dayoff = 55; $hroff = 2; $minoff = 17; $secoff = 5; // Base date $birthtime = mktime($hr, $min, $sec, $mon, $day, $year, TRUE); $year = date('Y', $birthtime) + $yroff; $mon = date('m', $birthtime) + $monoff; $day = date('d', $birthtime) + $dayoff; $hr = date('H', $birthtime) + $hroff; $min = date('i', $birthtime) + $minoff; $sec = date('s', $birthtime) + $secoff; // Offset date $then = mktime($hr, $min, $sec, $mon, $day, $year, TRUE); printf("Birthtime is: %s\nthen is: %s\n", date(DATE_RFC1123, $birthtime), date(DATE_RFC1123, $then)); // ------------ // 3. 'strtotime' // Generate timestamp whatever way is preferable $birthtime = mktime(3, 45, 50, 1, 18, 1973); $birthtime = strtotime('1/18/1973 03:45:50'); $then = strtotime('+55 days 2 hours 17 minutes 2 seconds', $birthtime); printf("Birthtime is: %s\nthen is: %s\n", date(DATE_RFC1123, $birthtime), date(DATE_RFC1123, $then)); // ------------ // 4. 'DateTime' class $birthtime = new DateTime('1/18/1973 03:45:50'); $then = new DateTime('1/18/1973 03:45:50'); $then->modify('+55 days 2 hours 17 minutes 2 seconds'); printf("Birthtime is: %s\nthen is: %s\n", $birthtime->format(DATE_RFC1123), $then->format(DATE_RFC1123)); // @@PLEAC@@_3.5 // Date intervals are most easily computed using timestamps [i.e. *NIX Epoch // Seconds] which, of course, gives the interval result is seconds from which // all other interval measures [days, weeks, months, years] may be derived. // Refer to previous section for discussion of daylight saving and other related // problems $interval_seconds = $recent - $earlier; // ---------------------------- // Conventional approach ... $bree = strtotime('16 Jun 1981, 4:35:25'); $nat = strtotime('18 Jan 1973, 3:45:50'); // ... or, with daylight saving adjustment $bree = mktime(4, 35, 25, 6, 16, 1981, TRUE); $nat = mktime(3, 45, 50, 1, 18, 1973, TRUE); $difference = $bree - $nat; // 'dateInterval' custom function computes intervals in several measures given an // interval in seconds. Note, 'month' and 'year' measures not provided printf("There were %d seconds between Nat and Bree\n", $difference); printf("There were %d weeks between Nat and Bree\n", dateInterval('weeks', $difference)); printf("There were %d days between Nat and Bree\n", dateInterval('days', $difference)); printf("There were %d hours between Nat and Bree\n", dateInterval('hours', $difference)); printf("There were %d minutes between Nat and Bree\n", dateInterval('mins', $difference)); // @@PLEAC@@_3.6 // 'getdate' accepts a timestamp [or implicitly calls 'time'] and returns an array of // date components. It returns much the same information as 'strptime' except that // the component names are different $today = getdate(); $weekday = $today['wday']; $monthday = $today['mday']; $yearday = $today['yday']; $weeknumber = (int) round($yearday / 7.0); // Safter method of obtaining week number $weeknumber = strftime('%U') + 1; // ---------------------------- define(SEP, '/'); $day = 16; $month = 6; $year = 1981; $timestamp = mktime(0, 0, 0, $month, $day, $year); $date = getdate($timestamp); $weekday = $date['wday']; $monthday = $date['mday']; $yearday = $date['yday']; $weeknumber = (int) round($yearday / 7.0); $weeknumber = strftime('%U', $timestamp) + 1; // Interpolate ... $sep = SEP; echo "{$month}{$sep}{$day}{$sep}{$year} was a {$date['weekday']} in week {$weeknumber}\n"; // ... or, concatenate echo $month . SEP . $day . SEP . $year . ' was a ' . $date['weekday'] . ' in week ' . $weeknumber . "\n"; // @@PLEAC@@_3.7 // 'strtotime' parses a textual date expression by attempting a 'best guess' at // the format, and either fails, or generates a timestamp. Timestamp could be fed // into any one of the various functions; example: $timestamp = strtotime('1998-06-03'); echo strftime('%Y-%m-%d', $timestamp) . "\n"; // 'strptime' parses a textual date expression according to a specified format, // and returns an array of date components; components can be easily dumped print_r(strptime('1998-06-03', '%Y-%m-%d')); // ---------------------------- // Parse date string according to format $darr = strptime('1998-06-03', '%Y-%m-%d'); if (!empty($darr)) { // Show date components in 'debug' form print_r($darr); // Check whether there was a parse error i.e. one or more components could not // be extracted from the string if (empty($darr['unparsed'])) { // Properly parsed date, so validate required components using, 'checkdate' if (checkdate($darr['tm_mon'] + 1, $darr['tm_mday'], $darr['tm_year'] + 1900)) echo "Parsed date verified as correct\n"; else echo "Parsed date failed verification\n"; } else { echo "Date string parse not complete; failed components: {$darr['unparsed']}\n"; } } else { echo "Date string could not be parsed\n"; } // @@PLEAC@@_3.8 // 'date' and 'strftime' both print a date string based on: // * Format String, describing layout of date components // * Timestamp [*NIX Epoch Seconds], either given explicitly, or implictly // via a call to 'time' which retrieves current time value $ts = 1234567890; date('Y/m/d', $ts); date('Y/m/d', mktime($h, $m, $s, $mth, $d, $y, $is_dst)); date('Y/m/d'); // same as: date('Y/m/d', time()); // ------------ $ts = 1234567890; strftime('%Y/%m/%d', $ts); strftime('%Y/%m/%d', mktime($h, $m, $s, $mth, $d, $y, $is_dst)); strftime('%Y/%m/%d'); // same as: strftime('%Y/%m/%d', time()); // ---------------------------- // 'mktime' creates a local time timestamp $t = strftime('%a %b %e %H:%M:%S %z %Y', mktime(3, 45, 50, 1, 18, 73, TRUE)); echo "{$t}\n"; // 'gmmktime' creates a GMT time timestamp $t = strftime('%a %b %e %H:%M:%S %z %Y', gmmktime(3, 45, 50, 1, 18, 73)); echo "{$t}\n"; // ---------------------------- // 'strtotime' parses a textual date expression, and generates a timestamp $t = strftime('%A %D', strtotime('18 Jan 1973, 3:45:50')); echo "{$t}\n"; // This should generate output identical to previous example $t = strftime('%A %D', mktime(3, 45, 50, 1, 18, 73, TRUE)); echo "{$t}\n"; // @@PLEAC@@_3.9 // PHP 5 and above can use the built-in, 'microtime'. Crude implementation for ealier versions: // function microtime() { $t = gettimeofday(); return (float) ($t['sec'] + $t['usec'] / 1000000.0); } // ------------ $before = microtime(); $line = fgets(STDIN); $elapsed = microtime() - $before; printf("You took %.3f seconds\n", $elapsed); // ------------ define(NUMBER_OF_TIMES, 100); define(SIZE, 500); for($i = 0; $i < NUMBER_OF_TIMES; $i++) { $arr = array(); for($j = 0; $j < SIZE; $j++) $arr[] = rand(); $begin = microtime(); sort($arr); $elapsed = microtime() - $begin; $total_time += $elapsed; } printf("On average, sorting %d random numbers takes %.5f seconds\n", SIZE, $total_time / (float) NUMBER_OF_TIMES); // @@PLEAC@@_3.10 // Low-resolution: sleep time specified in seconds sleep(1); // High-resolution: sleep time specified in microseconds [not reliable under Windows] usleep(250000); // @@PLEAC@@_3.11 // @@INCOMPLETE@@ // @@INCOMPLETE@@ // @@PLEAC@@_4.0 // Nested arrays are supported, and may be easily printed using 'print_r' $nested = array('this', 'that', 'the', 'other'); $nested = array('this', 'that', array('the', 'other')); print_r($nested); $tune = array('The', 'Star-Spangled', 'Banner'); // @@PLEAC@@_4.1 // PHP offers only the 'array' type which is actually an associative array, though // may be numerically indexed, to mimic vectors and matrices; there is no separate // 'list' type $a = array('quick', 'brown', 'fox'); // ------------ $a = escapeshellarg('Why are you teasing me?'); // ------------ $lines = << { } [ ]'); // Do this to quote each element within '..' // $brax = array_map('escapeshellarg', split(' ', '( ) < > { } [ ]')); $rings = split(' ', 'Nenya Narya Vilya'); $tags = split(' ', 'LI TABLE TR TD A IMG H1 P'); $sample = split(' ', 'The vertical bar | looks and behaves like a pipe.'); // @@PLEAC@@_4.2 function commify_series($list) { $n = str_word_count($list); $series = str_word_count($list, 1); if ($n == 0) return NULL; if ($n == 1) return $series[0]; if ($n == 2) return $series[0] . ' and ' . $series[1]; return join(', ', array_slice($series, 0, -1)) . ', and ' . $series[$n - 1]; } // ------------ echo commify_series('red') . "\n"; echo commify_series('red yellow') . "\n"; echo commify_series('red yellow green') . "\n"; $mylist = 'red yellow green'; echo 'I have ' . commify_series($mylist) . " marbles.\n"; // ---------------------------- function commify_series($arr) { $n = count($arr); $sepchar = ','; foreach($arr as $str) { if (strpos($str, ',') === false) continue; $sepchar = ';'; break; } if ($n == 0) return NULL; if ($n == 1) return $arr[0]; if ($n == 2) return $arr[0] . ' and ' . $arr[1]; return join("{$sepchar} ", array_slice($arr, 0, -1)) . "{$sepchar} and " . $arr[$n - 1]; } // ------------ $lists = array( array('just one thing'), split(' ', 'Mutt Jeff'), split(' ', 'Peter Paul Mary'), array('To our parents', 'Mother Theresa', 'God'), array('pastrami', 'ham and cheese', 'peanut butter and jelly', 'tuna'), array('recycle tired, old phrases', 'ponder big, happy thoughts'), array('recycle tired, old phrases', 'ponder big, happy thoughts', 'sleep and dream peacefully')); foreach($lists as $arr) { echo 'The list is: ' . commify_series($arr) . ".\n"; } // @@PLEAC@@_4.3 // AFAICT you cannot grow / shrink an array to an arbitrary size. However, you can: // * Grow an array by appending an element using subscrip notation, or using // either 'array_unshift' or 'array_push' to add one or more elements $arr[] = 'one'; array_unshift($arr, 'one', 'two', 'three'); array_push($arr, 'one', 'two', 'three'); // * Shrink an array by using 'unset' to remove one or more specific elements, or // either 'array_shift' or 'array_pop' to remove an element from the ends unset($arr[$idx1], $arr[$idx2], $arr[$idx3]); $item = array_shift($arr); $item = array_pop($arr); // ---------------------------- function what_about_the_array() { global $people; echo 'The array now has ' . count($people) . " elements\n"; echo 'The index value of the last element is ' . (count($people) - 1) . "\n"; echo 'Element #3 is ' . $people[3] . "\n"; } $people = array('Crosby', 'Stills', 'Nash', 'Young'); what_about_the_array(); array_pop($people); what_about_the_array(); // Cannot, AFAICT, resize the array to an arbitrary size # @@PLEAC@@_4.4 foreach ($list as $item) { // do something with $item } // Environment listing example // PHP defines a superglobal $_ENV to provide access to environment // variables. // Beware, array assignment means copying in PHP. You need to use // the reference operator to avoid copying. But we want a copy here. $env = $_ENV; // PHP can sort an array by key, so you don't need to get keys, // and then sort. ksort($env); foreach ($env as $key => $value) { echo "{$key}={$value}\n"; } // Literal translation of Perl example would be: $keys = array_keys($_ENV); sort($keys); foreach ($keys as $key) { echo "{$key}={$_ENV[$key]}\n"; } // This assumes that MAX_QUOTA is a named constant. foreach ($all_users as $user) { $disk_space = get_usage($user); if ($disk_space > MAX_QUOTA) { complain($user); } } // You can't modify array's elements in-place. $array = array(1, 2, 3); $newarray = array(); foreach ($array as $item) { $newarray[] = $item - 1; } print_r($newarray); // Before PHP 5, that is. You can precede the reference operator // before $item to get reference instead of copy. $array = array(1, 2, 3); foreach ($array as &$item) { $item--; } print_r($array); // TODO: explain the old each() and list() iteration construct. // foreach is new in PHP 4, and there are subtle differences. // @@PLEAC@@_4.5 // Conventional 'read-only' access foreach($array as $item) { ; // Can access, but not update, array element referred to by '$item' } // ---- // '&' makes '$item' a reference foreach($array as &$item) { ; // Update array element referred to by '$item' } // ------------ $arraylen = count($array); for($i = 0; $i < $arraylen; $i++) { ; // '$array' is updateable via subscript notation } // ---------------------------- $fruits = array('Apple', 'Raspberry'); foreach($fruits as &$fruit) { echo "{$fruit} tastes good in a pie.\n"; } $fruitlen = count($fruits); for($i = 0; $i < $fruitlen; $i++) { echo "{$fruits[$i]} tastes good in a pie.\n"; } // ---------------------------- $rogue_cats = array('Blackie', 'Goldie', 'Silkie'); // Take care to assign reference to '$rogue_cats' array via '=&' $namelist['felines'] =& $rogue_cats; // Take care to make '$cat' a reference via '&$' to allow updating foreach($namelist['felines'] as &$cat) { $cat .= ' [meow]'; } // Via array reference foreach($namelist['felines'] as $cat) { echo "{$cat} purrs hypnotically.\n"; } echo "---\n"; // Original array foreach($rogue_cats as $cat) { echo "{$cat} purrs hypnotically.\n"; } // @@PLEAC@@_4.6 // PHP offers the 'array_unique' function to perform this task. It works with both keyed, // and numerically-indexed arrays; keys / indexes are preserved; use of 'array_values' // is recommended to reindex numerically-indexed arrays since there will likely be missing // indexes // Remove duplicate values $unique = array_unique($array); // Remove duplicates, and reindex [for numerically-indexed arrays only] $unique = array_values(array_unique($array)); // or use: $unique = array_keys(array_flip($array)); // ---------------------------- // Selected Perl 'seen' examples foreach($list as $item) { if (!isset($seen[$item])) { $seen[$item] = TRUE; $unique[] = $item; } } // ------------ foreach($list as $item) { $seen[$item] || (++$seen[$item] && ($unique[] = $item)); } // ------------ function some_func($item) { ; // Do something with '$item' } foreach($list as $item) { $seen[$item] || (++$seen[$item] && some_func($item)); } // ---------------------------- foreach(array_slice(preg_split('/\n/', `who`), 0, -1) as $user_entry) { $user = preg_split('/\s/', $user_entry); $ucnt[$user[0]]++; } ksort($ucnt); echo "users logged in:\n"; foreach($ucnt as $user => $cnt) { echo "\t{$user} => {$cnt}\n"; } // @@PLEAC@@_4.7 // PHP offers the 'array_diff' and 'array_diff_assoc' functions to perform this task. Same // points as made about 'array_unique' apply here also $a = array('c', 'a', 'b', 'd'); $b = array('c', 'a', 'b', 'e'); $diff = array_diff($a, $b); // $diff -> [3] 'd' $diff = array_diff($b, $a); // $diff -> [3] 'e' // Numerically-indexed array, reindexed $diff = array_values(array_diff($a, $b)); // $diff -> [0] 'd' $diff = array_values(array_diff($b, $a)); // $diff -> [0] 'e' // ---------------------------- // 1st Perl 'seen' example only $a = array('k1' => 11, 'k2' => 12, 'k4' => 14); $b = array('k1' => 11, 'k2' => 12, 'k3' => 13); foreach($b as $item => $value) { $seen[$item] = 1; } // Stores key only e.g. $aonly[0] contains 'k4', same as Perl example foreach($a as $item => $value) { if (!$seen[$item]) $aonly[] = $item; } // Stores key and value e.g. $aonly['k4'] contains 14, same entry as in $a foreach($a as $item => $value) { if (!$seen[$item]) $aonly[$item] = $value; } // ---------------------------- // Conventional way: $hash = array('key1' => 1, 'key2' => 2); $hash['key1'] = 1; $hash['key2'] = 2; $hash = array_combine(array('key1', 'key2'), array(1, 2)); // ------------ $seen = array_slice($b, 0); $seen = array_combine(array_keys($b), array_fill(0, count($b), 1)); // @@PLEAC@@_4.8 // PHP offers a number of array-based 'set operation' functions: // * union: array_unique(array_merge(...)) // * intersection: array_intersect and family // * difference: array_diff and family // which may be used for this type of task. Also, if returned arrays need to be // reindexed, 'array_slice($array, 0)', or 'array_values($array)' are useful $a = array(1, 3, 5, 6, 7, 8); $b = array(2, 3, 5, 7, 9); $union = array_values(array_unique(array_merge($a, $b))); // 1, 2, 3, 5, 6, 7, 8, 9 $isect = array_values(array_intersect($a, $b)); // 3, 5, 7 $diff = array_values(array_diff($a, $b)); // 1, 8 // @@PLEAC@@_4.9 // PHP offers the 'array_merge' function to perform this task. Duplicate values are retained, // but if arrays are numerically-indexed, resulting array is reindexed $arr1 = array('c', 'a', 'b', 'd'); $arr2 = array('c', 'a', 'b', 'e'); $new = array_merge($arr1, $arr2); // $new -> 'c', 'a', 'b', 'd', 'c', 'a', 'b', 'd' // ---------------------------- $members = array('Time', 'Flies'); $initiates = array('An', 'Arrow'); $members = array_merge($members, $initiates); // ------------ $members = array('Time', 'Flies'); $initiates = array('An', 'Arrow'); // 'array_splice' is the PHP equivalent to Perl's 'splice' array_splice($members, 2, 0, array_merge(array('Like'), $initiates)); echo join(' ', $members) . "\n"; array_splice($members, 0, 1, array('Fruit')); array_splice($members, -2, 2, array('A', 'Banana')); echo join(' ', $members) . "\n"; // @@PLEAC@@_4.10 $reversed = array_reverse($array); // ---------------------------- foreach(array_reverse($array) as $item) { ; // ... do something with '$item' ... } // ------------ for($i = count($array) - 1; $i >= 0; $i--) { ; // ... do something with '$array[$i]' ... } // ---------------------------- sort($array); $array = array_reverse($array); // ------------ rsort($array); // @@PLEAC@@_4.11 // Array elements can be deleted using 'unset'; removing several elements would require applying // 'unset' several times, probably in a loop. However, they would most likely also need to be // reindexed, so a better approach would be to use 'array_slice' which avoids explicit looping. // Where elements need to be removed, and those elements also returned, it is probably best to // combine both operations in a function. This is the approach taken here in implementing both // 'shiftN' and 'popN', and it is these functions that are used in the examples function popN(&$arr, $n) { $ret = array_slice($arr, -($n), $n); $arr = array_slice($arr, 0, count($arr) - $n); return $ret; } function shiftN(&$arr, $n) { $ret = array_slice($arr, 0, $n); $arr = array_slice($arr, $n); return $ret; } // ------------ // Remove $n elements from the front of $array; return them in $fron $front = shiftN($array, $n); // Remove $n elements from the end of $array; return them in $end $end = popN($array, $n); // ------------ $friends = array('Peter', 'Paul', 'Mary', 'Jim', 'Tim'); list($this_, $that) = shiftN($friends, 2); echo "{$this_} {$that}\n"; // ------------ $beverages = array('Dew', 'Jolt', 'Cola', 'Sprite', 'Fresca'); $pair = popN($beverages, 2); echo join(' ', $pair) . "\n"; // @@PLEAC@@_4.12 // This section illustrates various 'find first' techniques. The Perl examples all use an // explicit loop and condition testing [also repeated here]. This is the simplest, and // [potentially] most efficient approach because the search can be terminated as soon as a // match is found. However, it is worth mentioning a few alternatives: // * 'array_search' performs a 'find first' using the element value rather than a condition // check, so isn't really applicable here // * 'array_filter', whilst using a condition check, actually performs a 'find all', though // all but the first returned element can be discarded. This approach is actually less error // prone than using a loop, but the disadvantage is that each element is visited: there is no // means of terminating the search once a match has been found. It would be nice if this // function were to have a third parameter, a Boolean flag indicating whether to traverse // the whole array, or quit after first match [next version, maybe :) ?] $found = FALSE; foreach($array as $item) { // Not found - skip to next item if (!$criterion) continue; // Found - save and leave $match = $item; $found = TRUE; break; } if ($found) { ; // do something with $match } else { ; // not found } // ------------ function predicate($element) { if (criterion) return TRUE; return FALSE; } $match = array_slice(array_filter($array, 'predicate'), 0, 1); if ($match) { ; // do something with $match[0] } else { ; // $match is empty - not found } // ---------------------------- class Employee { public $name, $age, $ssn, $salary; public function __construct($name, $age, $ssn, $salary, $category) { $this->name = $name; $this->age = $age; $this->ssn = $ssn; $this->salary = $salary; $this->category = $category; } } // ------------ $employees = array( new Employee('sdf', 27, 12345, 47000, 'Engineer'), new Employee('ajb', 32, 12376, 51000, 'Programmer'), new Employee('dgh', 31, 12355, 45000, 'Engineer')); // ------------ function array_update($arr, $lambda, $updarr) { foreach($arr as $key) $lambda($updarr, $key); return $updarr; } function highest_salaried_engineer(&$arr, $employee) { static $highest_salary = 0; if ($employee->category == 'Engineer') { if ($employee->salary > $highest_salary) { $highest_salary = $employee->salary; $arr[0] = $employee; } } } // ------------ // 'array_update' custom function is modelled on 'array_reduce' except that it allows the // return of an array, contents and length of which are entirely dependant on what the // callback function does. Here, it is logically working in a 'find first' capacity $highest_salaried_engineer = array_update($employees, 'highest_salaried_engineer', array()); echo 'Highest paid engineer is: ' . $highest_salaried_engineer[0]->name . "\n"; // @@PLEAC@@_4.13 // PHP implements 'grep' functionality [as embodied in the current section] in the 'array_filter' // function function predicate($element) { if (criterion) return TRUE; return FALSE; } $matching = array_filter($list, 'predicate'); // ------------ $bigs = array_filter($nums, create_function('$n', 'return $n > 1000000;')); // ------------ function is_pig($user) { $user_details = preg_split('/(\s)+/', $user); // Assuming field 5 is the resource being compared return $user_details[5] > 1e7; } $pigs = array_filter(array_slice(preg_split('/\n/', `who -u`), 0, -1), 'is_pig'); // ------------ $matching = array_filter(array_slice(preg_split('/\n/', `who`), 0, -1), create_function('$user', 'return preg_match(\'/^gnat /\', $user);')); // ------------ class Employee { public $name, $age, $ssn, $salary; public function __construct($name, $age, $ssn, $salary, $category) { $this->name = $name; $this->age = $age; $this->ssn = $ssn; $this->salary = $salary; $this->category = $category; } } // ------------ $employees = array( new Employee('sdf', 27, 12345, 47000, 'Engineer'), new Employee('ajb', 32, 12376, 51000, 'Programmer'), new Employee('dgh', 31, 12355, 45000, 'Engineer')); // ------------ $engineers = array_filter($employees, create_function('$employee', 'return $employee->category == "Engineer";')); // @@PLEAC@@_4.14 // PHP offers a rich set of sorting functions. Key features: // * Inplace sorts; the original array, not a a copy, is sorted // * Separate functions exist for sorting [both ascending and descending order]: // - By value, assign new keys / indices [sort, rsort] // - By key [ksort, krsort] (for non-numerically indexed arrays) // - By value [asort, arsort] // - As above, but using a user-defined comparator [i.e. callback function] // [usort, uasort, uksort] // - Natural order sort [natsort] // * Significantly, if sorting digit-only elements, whether strings or numbers, // 'natural order' [i.e. 1 before 10 before 100 (ascending)] is retained. If // the elements are alphanumeric e.g. 'z1', 'z10' then 'natsort' should be // used [note: beware of 'natsort' with negative numbers; prefer 'sort' or 'asort'] $unsorted = array(7, 12, -13, 2, 100, 5, 1, -2, 23, 3, 6, 4); sort($unsorted); // -13, -2, 1, 2, 3, 4, 5, 6, 7, 12, 23, 100 rsort($unsorted); // 100, 23, 12, 7, 6, 5, 4, 3, 2, 1, -2, -13 asort($unsorted); // -13, -2, 1, 2, 3, 4, 5, 6, 7, 12, 23, 100 arsort($unsorted); // 100, 23, 12, 7, 6, 5, 4, 3, 2, 1, -2, -13 natsort($unsorted); // -2, -13, 1, 2, 3, 4, 5, 6, 7, 12, 23, 100 // ------------ function ascend($left, $right) { return $left > $right; } function descend($left, $right) { return $left < $right; } // ------------ usort($unsorted, 'ascend'); // -13, -2, 1, 2, 3, 4, 5, 6, 7, 12, 23, 100 usort($unsorted, 'descend'); // 100, 23, 12, 7, 6, 5, 4, 3, 2, 1, -2, -13 uasort($unsorted, 'ascend'); // -13, -2, 1, 2, 3, 4, 5, 6, 7, 12, 23, 100 uasort($unsorted, 'descend'); // 100, 23, 12, 7, 6, 5, 4, 3, 2, 1, -2, -13 // ---------------------------- function kill_process($pid) { // Is 'killable' ? if (!posix_kill($pid, 0)) return; // Ok, so kill in two stages posix_kill($pid, 15); // SIGTERM sleep(1); posix_kill($pid, 9); // SIGKILL } function pid($pentry) { $p = preg_split('/\s/', trim($pentry)); return $p[0]; } $processes = array_map('pid', array_slice(preg_split('/\n/', `ps ax`), 1, -1)); sort($processes); echo join(' ,', $processes) . "\n"; echo 'Enter a pid to kill: '; if (($pid = trim(fgets(STDIN)))) kill_process($pid); // @@PLEAC@@_4.15 // Tasks in this section would typically use the PHP 'usort' family of functions // which are used with a comparator function so as to perform custom comparisions. // A significant difference from the Perl examples is that these functions are // inplace sorters, so it is the original array that is modified. Where this must // be prevented a copy of the array can be made and sorted function comparator($left, $right) { ; // Compare '$left' with '$right' returning result } // ------------ $ordered = array_slice($unordered); usort($ordered, 'comparator'); // ---------------------------- // The Perl example looks like it is creating a hash using computed values as the key, // array values as the value, sorting on the computed key, then extracting the sorted // values and placing them back into an array function compute($value) { ; // Return computed value utilising '$value' } // ------------ // Original numerically-indexed array [sample data used] $unordered = array(5, 3, 7, 1, 4, 2, 6); // Create hash using 'compute' function to generate the keys. This example assumes that // each value in the '$unordered' array is used in generating the corresponding '$key' foreach($unordered as $value) { $precomputed[compute($value)] = $value; } // Copy the hash, and sort it by key $ordered_precomputed = array_slice($precomputed, 0); ksort($ordered_precomputed); // Extract the values of the hash in current order placing them in a new numerically-indexed // array $ordered = array_values($ordered_precomputed); // ---------------------------- // As above, except uses 'array_update' and 'accum' to help create hash function array_update($arr, $lambda, $updarr) { foreach($arr as $key) $lambda($updarr, $key); return $updarr; } function accum(&$arr, $value) { $arr[compute($value)] = $value; } // ------------ function compute($value) { ; // Return computed value utilising '$value' } // ------------ // Original numerically-indexed array [sample data used] $unordered = array(5, 3, 7, 1, 4, 2, 6); // Create hash $precomputed = array_update($unordered, 'accum', array()); // Copy the hash, and sort it by key $ordered_precomputed = array_slice($precomputed, 0); ksort($ordered_precomputed); // Extract the values of the hash in current order placing them in a new numerically-indexed // array $ordered = array_values($ordered_precomputed); // ---------------------------- class Employee { public $name, $age, $ssn, $salary; public function __construct($name, $age, $ssn, $salary) { $this->name = $name; $this->age = $age; $this->ssn = $ssn; $this->salary = $salary; } } // ------------ $employees = array( new Employee('sdf', 27, 12345, 47000), new Employee('ajb', 32, 12376, 51000), new Employee('dgh', 31, 12355, 45000)); // ------------ $ordered = array_slice($employees, 0); usort($ordered, create_function('$left, $right', 'return $left->name > $right->name;')); // ------------ $sorted_employees = array_slice($employees, 0); usort($sorted_employees, create_function('$left, $right', 'return $left->name > $right->name;')); $bonus = array(12376 => 5000, 12345 => 6000, 12355 => 0); foreach($sorted_employees as $employee) { echo "{$employee->name} earns \${$employee->salary}\n"; } foreach($sorted_employees as $employee) { if (($amount = $bonus[$employee->ssn])) echo "{$employee->name} got a bonus of: \${$amount}\n"; } // ------------ $sorted = array_slice($employees, 0); usort($sorted, create_function('$left, $right', 'return $left->name > $right->name || $left->age != $right->age;')); // ---------------------------- // PHP offers a swag of POSIX functions for obtaining user information [i.e. they all read // the '/etc/passwd' file for the relevant infroamtion], and it is these that should rightly // be used for this purpose. However, since the intent of this section is to illustrate array // manipulation, these functions won't be used. Instead a custom function mimicing Perl's // 'getpwent' function will be implemented so the code presented here can more faithfully match // the Perl code function get_pw_entries() { function normal_users_only($e) { $entry = split(':', $e); return $entry[2] > 100 && $entry[2] < 32768; } foreach(array_filter(file('/etc/passwd'), 'normal_users_only') as $entry) $users[] = split(':', trim($entry)); return $users; } // ------------ $users = get_pw_entries(); usort($users, create_function('$left, $right', 'return $left[0] > $right[0];')); foreach($users as $user) echo "{$user[0]}\n"; // ---------------------------- $names = array('sdf', 'ajb', 'dgh'); $sorted = array_slice($names, 0); usort($sorted, create_function('$left, $right', 'return substr($left, 1, 1) > substr($right, 1, 1);')); // ------------ $strings = array('bbb', 'aa', 'c'); $sorted = array_slice($strings, 0); usort($sorted, create_function('$left, $right', 'return strlen($left) > strlen($right);')); // ---------------------------- function array_update($arr, $lambda, $updarr) { foreach($arr as $key) $lambda($updarr, $key); return $updarr; } function accum(&$arr, $value) { $arr[strlen($value)] = $value; } // ---- $strings = array('bbb', 'aa', 'c'); $temp = array_update($strings, 'accum', array()); ksort($temp); $sorted = array_values($temp); // ---------------------------- function array_update($arr, $lambda, $updarr) { foreach($arr as $key) $lambda($updarr, $key); return $updarr; } function accum(&$arr, $value) { if (preg_match('/(\d+)/', $value, $matches)) $arr[$matches[1]] = $value; } // ---- $fields = array('b1b2b', 'a4a', 'c9', 'ddd', 'a'); $temp = array_update($fields, 'accum', array()); ksort($temp); $sorted_fields = array_values($temp); // @@PLEAC@@_4.16 array_unshift($a1, array_pop($a1)); // last -> first array_push($a1, array_shift($a1)); // first -> last // ---------------------------- function grab_and_rotate(&$arr) { $item = $arr[0]; array_push($arr, array_shift($arr)); return $item; } // ------------ $processes = array(1, 2, 3, 4, 5); while (TRUE) { $process = grab_and_rotate($processes); echo "Handling process {$process}\n"; sleep(1); } // @@PLEAC@@_4.17 // PHP offers the 'shuffle' function to perform this task $arr = array(1, 2, 3, 4, 5, 6, 7, 8, 9); shuffle($arr); echo join(' ', $arr) . "\n"; // ---------------------------- // Perl example equivalents function fisher_yates_shuffle(&$a) { $size = count($a) - 1; for($i = $size; $i >= 0; $i--) { if (($j = rand(0, $i)) != $i) list($a[$i], $a[$j]) = array($a[$j], $a[$i]); } } function naive_shuffle(&$a) { $size = count($a); for($i = 0; $i < $size; $i++) { $j = rand(0, $size - 1); list($a[$i], $a[$j]) = array($a[$j], $a[$i]); } } // ------------ $arr = array(1, 2, 3, 4, 5, 6, 7, 8, 9); fisher_yates_shuffle($arr); echo join(' ', $arr) . "\n"; naive_shuffle($arr); echo join(' ', $arr) . "\n"; // @@PLEAC@@_4.18 // @@INCOMPLETE@@ // @@INCOMPLETE@@ // @@PLEAC@@_4.19 // @@INCOMPLETE@@ // @@INCOMPLETE@@ // @@PLEAC@@_5.0 // PHP uses the term 'array' to refer to associative arrays - referred to in Perl // as 'hashes' - and for the sake of avoiding confusion, the Perl terminology will // be used. As a matter of interest, PHP does not sport a vector, matrix, or list // type: the 'array' [Perl 'hash'] serves all these roles $age = array('Nat' => 24, 'Jules' => 25, 'Josh' => 17); $age['Nat'] = 24; $age['Jules'] = 25; $age['Josh'] = 17; $age = array_combine(array('Nat', 'Jules', 'Josh'), array(24, 25, 17)); // ------------ $food_colour = array('Apple' => 'red', 'Banana' => 'yellow', 'Lemon' => 'yellow', 'Carrot' => 'orange'); $food_colour['Apple'] = 'red'; $food_colour['Banana'] = 'yellow'; $food_colour['Lemon'] = 'yellow'; $food_colour['Carrot'] = 'orange'; $food_colour = array_combine(array('Apple', 'Banana', 'Lemon', 'Carrot'), array('red', 'yellow', 'yellow', 'orange')); // @@PLEAC@@_5.1 $hash[$key] = $value; // ------------ $food_colour = array('Apple' => 'red', 'Banana' => 'yellow', 'Lemon' => 'yellow', 'Carrot' => 'orange'); $food_colour['Raspberry'] = 'pink'; echo "Known foods:\n"; foreach($food_colour as $food => $colour) echo "{$food}\n"; // @@PLEAC@@_5.2 // Returns TRUE on all existing entries with non-NULL values if (isset($hash[$key])) ; // entry exists else ; // no such entry // ------------ // Returns TRUE on all existing entries regardless of attached value if (array_key_exists($key, $hash)) ; // entry exists else ; // no such entry // ---------------------------- $food_colour = array('Apple' => 'red', 'Banana' => 'yellow', 'Lemon' => 'yellow', 'Carrot' => 'orange'); foreach(array('Banana', 'Martini') as $name) { if (isset($food_colour[$name])) echo "{$name} is a food.\n"; else echo "{$name} is a drink.\n"; } // ---------------------------- $age = array('Toddler' => 3, 'Unborn' => 0, 'Phantasm' => NULL); foreach(array('Toddler', 'Unborn', 'Phantasm', 'Relic') as $thing) { echo "{$thing}:"; if (array_key_exists($thing, $age)) echo ' exists'; if (isset($age[$thing])) echo ' non-NULL'; if ($age[$thing]) echo ' TRUE'; echo "\n"; } // @@PLEAC@@_5.3 // Remove one, or more, hash entries unset($hash[$key]); unset($hash[$key1], $hash[$key2], $hash[$key3]); // Remove entire hash unset($hash); // ---------------------------- function print_foods() { // Perl example uses a global variable global $food_colour; $foods = array_keys($food_colour); echo 'Foods:'; foreach($foods as $food) echo " {$food}"; echo "\nValues:\n"; foreach($foods as $food) { $colour = $food_colour[$food]; if (isset($colour)) echo " {$colour}\n"; else echo " nullified or removed\n"; } } // ------------ $food_colour = array('Apple' => 'red', 'Banana' => 'yellow', 'Lemon' => 'yellow', 'Carrot' => 'orange'); echo "Initially:\n"; print_foods(); // Nullify an entry $food_colour['Banana'] = NULL; echo "\nWith 'Banana' nullified\n"; print_foods(); // Remove an entry unset($food_colour['Banana']); echo "\nWith 'Banana' removed\n"; print_foods(); // Destroy the hash unset($food_colour); // @@PLEAC@@_5.4 // Access keys and values foreach($hash as $key => $value) { ; // ... } // Access keys only foreach(array_keys($hash) as $key) { ; // ... } // Access values only foreach($hash as $value) { ; // ... } // ---------------------------- $food_colour = array('Apple' => 'red', 'Banana' => 'yellow', 'Lemon' => 'yellow', 'Carrot' => 'orange'); foreach($food_colour as $food => $colour) { echo "{$food} is {$colour}\n"; } foreach(array_keys($food_colour) as $food) { echo "{$food} is {$food_colour[$food]}\n"; } // ---------------------------- // 'countfrom' - count number of messages from each sender $line = fgets(STDIN); while (!feof(STDIN)) { if (preg_match('/^From: (.*)/', $line, $matches)) { if (isset($from[$matches[1]])) $from[$matches[1]] += 1; else $from[$matches[1]] = 1; } $line = fgets(STDIN); } if (isset($from)) { echo "Senders:\n"; foreach($from as $sender => $count) echo "{$sender} : {$count}\n"; } else { echo "No valid data entered\n"; } // @@PLEAC@@_5.5 // PHP offers, 'print_r', which prints hash contents in 'debug' form; it also // works recursively, printing any contained arrays in similar form // Array // ( // [key1] => value1 // [key2] => value2 // ... // ) print_r($hash); // ------------ // Based on Perl example; non-recursive, so contained arrays not printed correctly foreach($hash as $key => $value) { echo "{$key} => $value\n"; } // ---------------------------- // Sorted by keys // 1. Sort the original hash ksort($hash); // 2. Extract keys, sort, traverse original in key order $keys = array_keys($hash); sort($keys); foreach($keys as $key) { echo "{$key} => {$hash[$key]}\n"; } // Sorted by values // 1. Sort the original hash asort($hash); // 2. Extract values, sort, traverse original in value order [warning: finds // only first matching key in the case where duplicate values exist] $values = array_values($hash); sort($values); foreach($values as $value) { echo $value . ' <= ' . array_search($value, $hash) . "\n"; } // @@PLEAC@@_5.6 // Unless sorted, hash elements remain in the order of insertion. If care is taken to // always add a new element to the end of the hash, then element order is the order // of insertion. The following function, 'array_push_associative' [modified from original // found at 'array_push' section of PHP documentation], does just that function array_push_associative(&$arr) { foreach (func_get_args() as $arg) { if (is_array($arg)) foreach ($arg as $key => $value) { $arr[$key] = $value; $ret++; } else $arr[$arg] = ''; } return $ret; } // ------------ $food_colour = array(); // Individual calls, or ... array_push_associative($food_colour, array('Banana' => 'Yellow')); array_push_associative($food_colour, array('Apple' => 'Green')); array_push_associative($food_colour, array('Lemon' => 'Yellow')); // ... one call, one array; physical order retained // array_push_associative($food_colour, array('Banana' => 'Yellow', 'Apple' => 'Green', 'Lemon' => 'Yellow')); print_r($food_colour); echo "\nIn insertion order:\n"; foreach($food_colour as $food => $colour) echo " {$food} => {$colour}\n"; $foods = array_keys($food_colour); echo "\nStill in insertion order:\n"; foreach($foods as $food) echo " {$food} => {$food_colour[$food]}\n"; // @@PLEAC@@_5.7 foreach(array_slice(preg_split('/\n/', `who`), 0, -1) as $entry) { list($user, $tty) = preg_split('/\s/', $entry); $ttys[$user][] = $tty; // Could instead do this: // $user = array_slice(preg_split('/\s/', $entry), 0, 2); // $ttys[$user[0]][] = $user[1]; } ksort($ttys); // ------------ foreach($ttys as $user => $all_ttys) { echo "{$user}: " . join(' ', $all_ttys) . "\n"; } // ------------ foreach($ttys as $user => $all_ttys) { echo "{$user}: " . join(' ', $all_ttys) . "\n"; foreach($all_ttys as $tty) { $stat = stat('/dev/$tty'); $pwent = posix_getpwuid($stat['uid']); $user = isset($pwent['name']) ? $pwent['name'] : 'Not available'; echo "{$tty} owned by: {$user}\n"; } } // @@PLEAC@@_5.8 // PHP offers the 'array_flip' function to perform the task of exchanging the keys / values // of a hash i.e. invert or 'flip' a hash $reverse = array_flip($hash); // ---------------------------- $surname = array('Babe' => 'Ruth', 'Mickey' => 'Mantle'); $first_name = array_flip($surname); echo "{$first_name['Mantle']}\n"; // ---------------------------- $argc == 2 || die("usage: {$argv[0]} food|colour\n"); $given = $argv[1]; $colour = array('Apple' => 'red', 'Banana' => 'yellow', 'Lemon' => 'yellow', 'Carrot' => 'orange'); $food = array_flip($colour); if (isset($colour[$given])) echo "{$given} is a food with colour: {$colour[$given]}\n"; if (isset($food[$given])) echo "{$food[$given]} is a food with colour: {$given}\n"; // ---------------------------- $food_colour = array('Apple' => 'red', 'Banana' => 'yellow', 'Lemon' => 'yellow', 'Carrot' => 'orange'); foreach($food_colour as $food => $colour) { $foods_with_colour[$colour][] = $food; } $colour = 'yellow'; echo "foods with colour {$colour} were: " . join(' ', $foods_with_colour[$colour]) . "\n"; // @@PLEAC@@_5.9 // PHP implements a swag of sorting functions, most designed to work with numerically-indexed // arrays. For sorting hashes, the 'key' sorting functions are required: // * 'ksort', 'krsort', 'uksort' // Ascending order ksort($hash); // Descending order [i.e. reverse sort] krsort($hash); // Comparator-based sort function comparator($left, $right) { // Compare left key with right key return $left > $right; } uksort($hash, 'comparator'); // ---------------------------- $food_colour = array('Apple' => 'red', 'Banana' => 'yellow', 'Lemon' => 'yellow', 'Carrot' => 'orange'); // ------------ ksort($food_colour); foreach($food_colour as $food => $colour) { echo "{$food} is {$colour}\n"; } // ------------ uksort($food_colour, create_function('$left, $right', 'return $left > $right;')); foreach($food_colour as $food => $colour) { echo "{$food} is {$colour}\n"; } // @@PLEAC@@_5.10 // PHP offers the 'array_merge' function for this task [a related function, 'array_combine', // may be used to create a hash from an array of keys, and one of values, respectively] // Merge two, or more, arrays $merged = array_merge($a, $b, $c); // Create a hash from array of keys, and of values, respectively $hash = array_combine($keys, $values); // ------------ // Can always merge arrays manually foreach(array($h1, $h2, $h3) as $hash) { foreach($hash as $key => $value) { // If same-key values differ, only latest retained $merged[$key] = $value; // Do this to append values for that key // $merged[$key][] = $value; } } // ---------------------------- $food_colour = array('Apple' => 'red', 'Banana' => 'yellow', 'Lemon' => 'yellow', 'Carrot' => 'orange'); $drink_colour = array('Galliano' => 'yellow', 'Mai Tai' => 'blue'); // ------------ $ingested_colour = array_merge($food_colour, $drink_colour); // ------------ $substance_colour = array(); foreach(array($food_colour, $drink_colour) as $hash) { foreach($hash as $substance => $colour) { if (array_key_exists($substance, $substance_colour)) { echo "Warning {$substance_colour[$substance]} seen twice. Using first definition.\n"; continue; } $substance_colour[$substance] = $colour; } } // @@PLEAC@@_5.11 // PHP offers a number of array-based 'set operation' functions: // * union: array_merge // * intersection: array_intersect and family // * difference: array_diff and family // which may be used for this type of task // Keys occurring in both hashes $common = array_intersect_key($h1, $h2); // Keys occurring in the first hash [left side], but not in the second hash $this_not_that = array_diff_key($h1, $h2); // ---------------------------- $food_colour = array('Apple' => 'red', 'Banana' => 'yellow', 'Lemon' => 'yellow', 'Carrot' => 'orange'); $citrus_colour = array('Lemon' => 'yellow', 'Orange' => 'orange', 'Lime' => 'green'); $non_citrus = array_diff_key($food_colour, $citrus_colour); // @@PLEAC@@_5.12 // PHP implements a special type known as a 'resource' that encompasses things like file handles, // sockets, database connections, and many others. The 'resource' type is, essentially, a // reference variable that is not readily serialisable. That is to say: // * A 'resource' may be converted to a string representation via the 'var_export' function // * That same string cannot be converted back into a 'resource' // So, in terms of array handling, 'resource' types may be stored as array reference values, // but cannot be used as keys. // // I suspect it is this type of problem that the Perl::Tie package helps resolve. However, since // PHP doesn't, AFAIK, sport a similar facility, the examples in this section cannot be // implemented using file handles as keys $filenames = array('/etc/termcap', '/vmlinux', '/bin/cat'); foreach($filenames as $filename) { if (!($fh = fopen($filename, 'r'))) continue; // Cannot do this as required by the Perl code: // $name[$fh] = $filename; // Ok $name[$filename] = $fh; } // Would traverse array via: // // foreach(array_keys($name) as $fh) // ... // or // // foreach($name as $fh => $filename) // ... // but since '$fh' cannot be a key, either of these will work: // // foreach($name as $filename => $fh) // or foreach(array_values($name) as $fh) { fclose($fh); } // @@PLEAC@@_5.13 // PHP hashes are dynamic expanding and contracting as entries are added, and removed, // respectively. Thus, there is no need to presize a hash, nor is there, AFAIK, any // means of doing so except by the number of datums used when defining the hash // zero elements $hash = array(); // ------------ // three elements $hash = array('Apple' => 'red', 'Lemon' => 'yellow', 'Carrot' => 'orange'); // @@PLEAC@@_5.14 foreach($array as $element) $count[$element] += 1; // @@PLEAC@@_5.15 $father = array('Cain' => 'Adam', 'Abel' => 'Adam', 'Seth' => 'Adam', 'Enoch' => 'Cain', 'Irad' => 'Enoch', 'Mehujael' => 'Irad', 'Methusael'=> 'Mehujael', 'Lamech' => 'Methusael', 'Jabal' => 'Lamech', 'Jubal' => 'Lamech', 'Tubalcain' => 'Lamech', 'Enos' => 'Seth'); // ------------ $name = trim(fgets(STDIN)); while (!feof(STDIN)) { while (TRUE) { echo "$name\n"; // Can use either: if (!isset($father[$name])) break; $name = $father[$name]; // or: // if (!key_exists($name, $father)) break; // $name = $father[$name]; // or combine the two lines: // if (!($name = $father[$name])) break; } echo "\n"; $name = trim(fgets(STDIN)); } // ---------------------------- define(SEP, ' '); foreach($father as $child => $parent) { if (!$children[$parent]) $children[$parent] = $child; else $children[$parent] .= SEP . $child; } $name = trim(fgets(STDIN)); while (!feof(STDIN)) { echo $name . ' begat '; if (!$children[$name]) echo "Nothing\n" else echo str_replace(SEP, ', ', $children[$name]) . "\n"; $name = trim(fgets(STDIN)); } // ---------------------------- define(SEP, ' '); $files = array('/tmp/a', '/tmp/b', '/tmp/c'); foreach($files as $file) { if (!is_file($file)) { echo "Skipping {$file}\n"; continue; } if (!($fh = fopen($file, 'r'))) { echo "Skipping {$file}\n"; continue; } $line = fgets($fh); while (!feof($fh)) { if (preg_match('/^\s*#\s*include\s*<([^>]+)>/', $line, $matches)) { if (isset($includes[$matches[1]])) $includes[$matches[1]] .= SEP . $file; else $includes[$matches[1]] = $file; } $line = fgets($fh); } fclose($fh); } print_r($includes); // @@PLEAC@@_5.16 // @@INCOMPLETE@@ // @@INCOMPLETE@@ // @@PLEAC@@_9.0 $entry = stat('/bin/vi'); $entry = stat('/usr/bin'); $entry = stat($argv[1]); // ------------ $entry = stat('/bin/vi'); $ctime = $entry['ctime']; $size = $entry['size']; // ---------------------------- // For the simple task of determining whether a file contains, text', a simple // function that searches for a newline could be implemented. Not exactly // foolproof, but very simple, low overhead, no installation headaches ... function containsText($file) { $status = FALSE; if (($fp = fopen($file, 'r'))) { while (FALSE !== ($char = fgetc($fp))) { if ($char == "\n") { $status = TRUE; break; } } fclose($fp); } return $status; } // PHP offers the [currently experimental] Fileinfo group of functions to // determine file types based on their contents / 'magic numbers'. This // is functionality similar to the *NIX, 'file' utility. Note that it must // first be installed using the PEAR utility [see PHP documentation] function isTextFile($file) { // Note: untested code, but I believe this is how it is supposed to work $finfo = finfo_open(FILEINFO_NONE); $status = (finfo_file($finfo, $file) == 'ASCII text'); finfo_close($finfo); return $status; } // Alternatively, use the *NIX utility, 'file', directly function isTextFile($file) { return exec(trim('file -bN ' . escapeshellarg($file))) == 'ASCII text'; } // ---- containsText($argv[1]) || die("File {$argv[1]} doesn't have any text in it\n"); isTextFile($argv[1]) || die("File {$argv[1]} doesn't have any text in it\n"); // ---------------------------- $dirname = '/usr/bin/'; ($dirhdl = opendir($dirname)) || die("Couldn't open {$dirname}\n"); while (($file = readdir($dirhdl)) !== FALSE) { printf("Inside %s is something called: %s\n", $dirname, $file); } closedir($dirhdl); // @@PLEAC@@_9.1 $filename = 'example.txt'; // Get the file's current access and modification time, respectively $fs = stat($filename); $readtime = $fs['atime']; $writetime = $fs['mtime']; // Alter $writetime, and $readtime ... // Update file timestamp touch($filename, $writetime, $readtime); // ---------------------------- $filename = 'example.txt'; // Get the file's current access and modification time, respectively $fs = stat($filename); $atime = $fs['atime']; $mtime = $fs['mtime']; // Dedicated functions also exist to retrieve this information: // // $atime = $fileatime($filename); // $mtime = $filemtime($filename); // // Perform date arithmetic. Traditional approach where arithmetic is performed // directly with Epoch Seconds [i.e. the *NIX time stamp value] will work ... define('SECONDS_PER_DAY', 60 * 60 * 24); // Set file's access and modification times to 1 week ago $atime -= 7 * SECONDS_PER_DAY; $mtime -= 7 * SECONDS_PER_DAY; // ... but care must be taken to account for daylight saving. Therefore, the // recommended approach is to use library functions to perform such tasks: $atime = strtotime('-7 days', $atime); $mtime = strtotime('-7 days', $mtime); // Update file timestamp touch($filename, $mtime, $atime); // Good idea to clear the cache after such updates have occurred so fresh // values will be retrieved on next access clearstatcache(); // ---------------------------- $argc == 2 || die("usage: {$argv[0]} filename\n"); $filename = $argv[1]; $fs = stat($filename); $atime = $fs['atime']; $mtime = $fs['mtime']; // Careful here: since interactive, use, 'system', not 'exec', to launch [latter // does not work under *NIX - at least, not for me :)] system(trim(getenv('EDITOR') . ' vi ' . escapeshellarg($filename)), $retcode); touch($filename, $mtime, $atime) || die("Error updating timestamp on file, {$filename}!\n"); // @@PLEAC@@_9.2 // The 'unlink' function is used to delete regular files, whilst the 'rmdir' function // does the same on non-empty directories. AFAIK, no recursive-deletion facility // exists, and must be manually programmed $filename = '...'; @unlink($filename) || die("Can't delete, {$filename}!\n"); // ------------ $files = glob('...'); $problem = FALSE; // Could simply use a foreach loop foreach($files as $filename) { @unlink($filename) || $problem = TRUE; } // // Alternatively, an applicative approach could be used, one closer in spirit to // largely-functional languages like Scheme // // function is_all_deleted($deleted, $filename) { return @unlink($filename) && $deleted; } // $problem = !array_reduce($files, 'is_all_deleted', TRUE); // if ($problem) { fwrite(STDERR, 'Could not delete all of:'); foreach($files as $filename) { fwrite(STDERR, ' ' . $filename); } fwrite(STDERR, "\n"); exit(1); } // ------------ function rmAll($files) { $count = 0; foreach($files as $filename) { @unlink($filename) && $count++; }; return $count; // An applicative alternative using 'create_function', PHP's rough equivalent of 'lambda' ... // // return array_reduce($files, // create_function('$count, $filename', 'return @unlink($filename) && $count++;'), 0); } // ---- $files = glob('...'); $toBeDeleted = sizeof($files); $count = rmAll($files); ($count == $toBeDeleted) || die("Could only delete {$count} of {$toBeDeleted} files\n"); // @@PLEAC@@_9.3 $oldfile = '/tmp/old'; $newfile = '/tmp/new'; copy($oldfile, $newfile) || die("Error copying file\n"); // ---------------------------- // All the following copy a file by copying its contents. Examples do so in a single // operation, but it is also possible to copy arbitrary blocks, or, line-by-line in // the case of 'text' files $oldfile = '/tmp/old'; $newfile = '/tmp/new'; if (is_file($oldfile)) file_put_contents($newfile, file_get_contents($oldfile)); else die("Problem copying file {$oldfile} to file {$newfile}\n"); // ------------ $oldfile = '/tmp/old'; $newfile = '/tmp/new'; fwrite(($nh = fopen($newfile, 'wb')), fread(($oh = fopen($oldfile, 'rb')), filesize($oldfile))); fclose($oh); fclose($nh); // ------------ // As above, but with some error checking / handling $oldfile = '/tmp/old'; $newfile = '/tmp/new'; ($oh = fopen($oldfile, 'rb')) || die("Problem opening input file {$oldfile}\n"); ($nh = fopen($newfile, 'wb')) || die("Problem opening output file {$newfile}\n"); if (($filesize = filesize($oldfile)) > 0) { fwrite($nh, fread($oh, $filesize)) || die("Problem reading / writing file data\n"); } fclose($oh); fclose($nh); // ---------------------------- // Should there be platform-specfic problems copying 'very large' files, it is // a simple matter to call a system command utility via, 'exec' // *NIX-specific example. Could check whether, 'exec', succeeded, but checking whether // a file exists after the operation might be a better approach $oldfile = '/tmp/old'; $newfile = '/tmp/new'; is_file($newfile) && unlink($newfile); exec(trim('cp --force ' . escapeshellarg($oldfile) . ' ' . escapeshellarg($newfile))); is_file($newfile) || die("Problem copying file {$oldfile} to file {$newfile}\n"); // For other operating systems just change: // * filenames // * command being 'exec'ed // as the rest of the code is platform independant // @@PLEAC@@_9.4 function makeDevInodePair($filename) { if (!($fs = @stat($filename))) return FALSE; return strval($fs['dev'] . $fs['ino']); } // ------------ function do_my_thing($filename) { // Using a global variable to mimic Perl example, but could easily have passed // '$seen' as an argument global $seen; $devino = makeDevInodePair($filename); // Process $filename if it has not previously been seen, else just increment if (!isset($seen[$devino])) { // ... process $filename ... // Set initial count $seen[$devino] = 1; } else { // Otherwise, just increment the count $seen[$devino] += 1; } } // ---- // Simple example $seen = array(); do_my_thing('/tmp/old'); do_my_thing('/tmp/old'); do_my_thing('/tmp/old'); do_my_thing('/tmp/new'); foreach($seen as $devino => $count) { echo "{$devino} -> {$count}\n"; } // ------------ // A variation on the above avoiding use of global variables, and illustrating use of // easily-implemented 'higher order' techniques // Helper function loosely modelled on, 'array_reduce', but using an array as // 'accumulator', which is returned on completion function array_update($arr, $lambda, $updarr) { foreach($arr as $key) $lambda($updarr, $key); return $updarr; } function do_my_thing(&$seen, $filename) { if (!array_key_exists(($devino = makeDevInodePair($filename)), $seen)) { // ... processing $filename ... // Update $seen $seen[$devino] = 1; } else { // Update $seen $seen[$devino] += 1; } } // ---- // Simple example $files = array('/tmp/old', '/tmp/old', '/tmp/old', '/tmp/new'); // Could do this ... $seen = array(); array_update($files, 'do_my_thing', &$seen); // or this: $seen = array_update($files, 'do_my_thing', array()); // or a 'lambda' could be used: array_update($files, create_function('$seen, $filename', '... code not shown ...'), &$seen); foreach($seen as $devino => $count) { echo "{$devino} -> {$count}\n"; } // ---------------------------- $files = glob('/tmp/*'); define(SEP, ';'); $seen = array(); foreach($files as $filename) { if (!array_key_exists(($devino = makeDevInodePair($filename)), $seen)) $seen[$devino] = $filename; else $seen[$devino] = $seen[$devino] . SEP . $filename; } $devino = array_keys($seen); sort($devino); foreach($devino as $key) { echo $key . ':'; foreach(split(SEP, $seen[$key]) as $filename) echo ' ' . $filename; echo "\n"; } // @@PLEAC@@_9.5 // Conventional POSIX-like approach to directory traversal $dirname = '/usr/bin/'; ($dirhdl = opendir($dirname)) || die("Couldn't open {$dirname}\n"); while (($file = readdir($dirhdl)) !== FALSE) { ; // ... do something with $dirname/$file // ... } closedir($dirhdl); // ------------ // Newer [post PHP 4], 'applicative' approach - an array of filenames is // generated that may be processed via external loop ... $dirname = '/usr/bin/'; foreach(scandir($dirname) as $file) { ; // ... do something with $dirname/$file // ... } // .. or, via callback application, perhaps after massaging by one of the // 'array' family of functions [also uses, 'array_update', from earlier section] $newlist = array_update(array_reverse(scandir($dirname)), create_function('$filelist, $file', ' ; '), array()); // And don't forget that the old standby, 'glob', that returns an array of // paths filtered using the Bourne Shell-based wildcards, '?' and '*', is // also available foreach(glob($dirname . '*') as $path) { ; // ... do something with $path // ... } // ---------------------------- // Uses, 'isTextFile', from an earlier section $dirname = '/usr/bin/'; echo "Text files in {$dirname}:\n"; foreach(scandir($dirname) as $file) { // Take care when constructing paths to ensure cross-platform operability $path = $dirname . $file; if (is_file($path) && isTextFile($path)) echo $path . "\n"; } // ---------------------------- function plain_files($dirname) { ($dirlist = glob($dirname . '*')) || die("Couldn't glob {$dirname}\n"); // Pass function name directly if only a single function performs filter test return array_filter($dirlist, 'is_file'); // Use, 'create_function', if a multi-function test is needed // // return array_filter($dirlist, create_function('$path', 'return is_file($path);')); // } // ------------ foreach(plain_files('/tmp/') as $path) { echo $path . "\n"; } // @@PLEAC@@_9.6 $dirname = '/tmp/'; // Full paths $pathlist = glob($dirname . '*.c'); // File names only - glob-based matching $filelist = array_filter(scandir($dirname), create_function('$file', 'return fnmatch("*.c", $file);')); // ---------------------------- $dirname = '/tmp/'; // File names only - regex-based matching [case-insensitive] $filelist = array_filter(scandir($dirname), create_function('$file', 'return eregi("\.[ch]$", $file);')); // ---------------------------- $dirname = '/tmp/'; // Directory names - all-digit names $dirs = array_filter(glob($dirname . '*', GLOB_ONLYDIR), create_function('$path', 'return ereg("^[0-9]+$", basename($path));')); // @@PLEAC@@_9.7 // Recursive directory traversal function and helper: traverses a directory tree // applying a function [and a variable number of accompanying arguments] to each // file class Accumulator { public $value; public function __construct($start_value) { $this->value = $start_value; } } // ------------ function process_directory_($op, $func_args) { if (is_dir($func_args[0])) { $current = $func_args[0]; foreach(scandir($current) as $entry) { if ($entry == '.' || $entry == '..') continue; $func_args[0] = $current . '/' . $entry; process_directory_($op, $func_args); } } else { call_user_func_array($op, $func_args); } } function process_directory($op, $dir) { if (!is_dir($dir)) return FALSE; $func_args = array_slice(func_get_args(), 1); process_directory_($op, $func_args); return TRUE; } // ---------------------------- $dirlist = array('/tmp/d1', '/tmp/d2', '/tmp/d3'); // Do something with each directory in the list foreach($dirlist as $dir) { ; // Delete directory [if empty] -> rmdir($dir); // Make it the 'current directory' -> chdir($dir); // Get list of files it contains -> $filelist = scandir($dir); // Get directory metadata -> $ds = stat($dir); } // ------------ $dirlist = array('/tmp/d1', '/tmp/d2', '/tmp/d3'); function pf($path) { // ... do something to the file or directory ... printf("%s\n", $path); } // For each directory in the list ... foreach($dirlist as $dir) { // Is this a valid directory ? if (!is_dir($dir)) { printf("%s does not exist\n", $dir); continue; } // Ok, so get all the directory's entries $filelist = scandir($dir); // An 'empty' directory will contain at least two entries: '..' and '.' if (count($filelist) == 2) { printf("%s is empty\n", $dir); continue; } // For each file / directory in the directory ... foreach($filelist as $file) { // Ignore '..' and '.' entries if ($file == '.' || $file == '..') continue; // Apply function to process the file / directory pf($dir . '/' . $file); } } // ---------------------------- function accum_filesize($file, $accum) { is_file($file) && ($accum->value += filesize($file)); } // ------------ // Verify arguments ... $argc == 2 || die("usage: {$argv[0]} dir\n"); $dir = $argv[1]; is_dir($dir) || die("{$dir} does not exist / not a directory\n"); // Collect data [use an object to accumulate results] $dirsize = new Accumulator(0); process_directory('accum_filesize', $dir, $dirsize); // Report results printf("%s contains %d bytes\n", $dir, $dirsize->value); // ---------------------------- function biggest_file($file, $accum) { if (is_file($file)) { $fs = filesize($file); if ($accum->value[1] < $fs) { $accum->value[0] = $file; $accum->value[1] = $fs; } } } // ------------ // Verify arguments ... $argc == 2 || die("usage: {$argv[0]} dir\n"); $dir = $argv[1]; is_dir($dir) || die("{$dir} does not exist / not a directory\n"); // Collect data [use an object to accumulate results] $biggest = new Accumulator(array('', 0)); process_directory('biggest_file', $dir, $biggest); // Report results printf("Biggest file is %s containing %d bytes\n", $biggest->value[0], $biggest->value[1]); // ---------------------------- function youngest_file($file, $accum) { if (is_file($file)) { $fct = filectime($file); if ($accum->value[1] > $fct) { $accum->value[0] = $file; $accum->value[1] = $fct; } } } // ------------ // Verify arguments ... $argc == 2 || die("usage: {$argv[0]} dir\n"); $dir = $argv[1]; is_dir($dir) || die("{$dir} does not exist / not a directory\n"); // Collect data [use an object to accumulate results] $youngest = new Accumulator(array('', 2147483647)); process_directory('youngest_file', $dir, $youngest); // Report results printf("Youngest file is %s dating %s\n", $youngest->value[0], date(DATE_ATOM, $youngest->value[1])); // @@PLEAC@@_9.8 // AFAICT, there is currently no library function that recursively removes a // directory tree [i.e. a directory, it's subdirectories, and any other files] // with a single call. Such a function needs to be custom built. PHP tools // with which to do this: // * 'unlink', 'rmdir', 'is_dir', and 'is_file' functions, will all take care // of the file testing and deletion // * Actual directory traversal requires obtaining directory / subdirectory // lists, and here there is much choice available, though care must be taken // as each has it's own quirks // - 'opendir', 'readdir', 'closedir' // - 'scandir' // - 'glob' // - SPL 'directory iterator' classes [newish / experimental - not shown here] // // The PHP documentation for 'rmdir' contains several examples, each illustrating // one of each approach; the example shown here is loosely based on one of these // examples // Recursor - recursively traverses directory tree function rmtree_($dir) { $dir = "$dir"; if ($dh = opendir($dir)) { while (FALSE !== ($item = readdir($dh))) { if ($item != '.' && $item != '..') { $subdir = $dir . '/' . "$item"; if (is_dir($subdir)) rmtree_($subdir); else @unlink($subdir); } } closedir($dh); @rmdir($dir); } } // Launcher - performs validation then starts recursive routine function rmtree($dir) { if (is_dir($dir)) { (substr($dir, -1, 1) == '/') && ($dir = substr($dir, 0, -1)); rmtree_($dir); return !is_dir($dir); } return FALSE; } // ------------ $argc == 2 || die("usage: rmtree dir\n"); rmtree($argv[1]) || die("Could not remove directory {$argv[1]}\n"); // @@PLEAC@@_9.9 $filepairs = array('x.txt' => 'x2.txt', 'y.txt' => 'y.doc', 'zxc.txt' => 'cxz.txt'); foreach($filepairs as $oldfile => $newfile) { @rename($oldfile, $newfile) || fwrite(STDERR, sprintf("Could not rename %s to %s\n", $oldfile, $newfile)); } // ---------------------------- // Call a system command utility via, 'exec'. *NIX-specific example. Could check whether, // 'exec', succeeded, but checking whether a renamed file exists after the operation might // be a better approach $oldfile = '/tmp/old'; $newfile = '/tmp/new'; is_file($newfile) && unlink($newfile); exec(trim('mv --force ' . escapeshellarg($oldfile) . ' ' . escapeshellarg($newfile))); is_file($oldfile) || die("Problem renaming file {$oldfile} to file {$newfile}\n"); // For other operating systems just change: // * filenames // * command being 'exec'ed // as the rest of the code is platform independant // ---------------------------- // A modified implementation of Larry's Filename Fixer. Rather than passing // a single expression, a 'from' regexp is passed; each match in the file // name(s) is changed to the value of 'to'. It otherwise behaves the same // $argc > 2 || die("usage: rename from to [file ...]\n"); $from = $argv[1]; $to = $argv[2]; if (count(($argv = array_slice($argv, 3))) < 1) while (!feof(STDIN)) $argv[] = substr(fgets(STDIN), 0, -1); foreach($argv as $file) { $was = $file; $file = ereg_replace($from, $to, $file); if (strcmp($was, $file) != 0) @rename($was, $file) || fwrite(STDERR, sprintf("Could not rename %s to %s\n", $was, $file)); } // @@PLEAC@@_9.10 $base = basename($path); $dir = dirname($path); // PHP's equivalent to Perl's 'fileparse' $pathinfo = pathinfo($path); $base = $pathinfo['basename']; $dir = $pathinfo['dirname']; $ext = $pathinfo['extension']; // ---------------------------- $path = '/usr/lib/libc.a'; printf("dir is %s, file is %s\n", dirname($path), basename($path)); // ------------ $path = '/usr/lib/libc.a'; $pathinfo = pathinfo($path); printf("dir is %s, name is %s, extension is %s\n", $pathinfo['dirname'], $pathinfo['basename'], $pathinfo['extension']); // ---------------------------- // Handle Mac example as a simple parse task. However, AFAIK, 'pathinfo' is cross-platform, // so should handle file path format differences transparently $path = 'Hard%20Drive:System%20Folder:README.txt'; $macp = array_combine(array('drive', 'folder', 'filename'), split("\:", str_replace('%20', ' ', $path))); $macf = array_combine(array('name', 'extension'), split("\.", $macp['filename'])); printf("dir is %s, name is %s, extension is %s\n", ($macp['drive'] . ':' . $macp['folder']), $macf['name'], ('.' . $macf['extension'])); // ---------------------------- // Not really necessary since we have, 'pathinfo', but better matches Perl example function file_extension($filename, $separator = '.') { return end(split(("\\" . $separator), $filename)); } // ---- echo file_extension('readme.txt') . "\n"; // @@PLEAC@@_9.11 // @@INCOMPLETE@@ // @@INCOMPLETE@@ // @@PLEAC@@_9.12 // @@INCOMPLETE@@ // @@INCOMPLETE@@ // @@PLEAC@@_10.0 // Since defined at outermost scope, $greeted may be considered a global variable $greeted = 0; // ------------ // Must use, 'global', keyword to inform functions that $greeted already exists as // a global variable. If this is not done, a local variable of that name is implicitly // defined function howManyGreetings() { global $greeted; return $greeted; } function hello() { global $greeted; $greeted++; echo "high there!, this procedure has been called {$greeted} times\n"; } // ------------ hello(); $greetings = howManyGreetings(); echo "bye there!, there have been {$greetings} greetings so far\n"; // @@PLEAC@@_10.1 // Conventionally-defined function together with parameter list function hypotenuse($side1, $side2) { return sqrt(pow($side1, 2) + pow($side2, 2)); } // ---- // Alternative is to define the function without parameter list, then use // 'func_get_arg' to extract arguments function hypotenuse() { // Could check number of arguments passed with: 'func_num_args', which // would be the approach used if dealing with variable number of arguments $side1 = func_get_arg(0); $side2 = func_get_arg(1); return sqrt(pow($side1, 2) + pow($side2, 2)); } // ------------ // 1. Conventional function call $diag = hypotenuse(3, 4); // ------------ // 2. Function call using, 'call_user_func' library routines $funcname = 'hypotenuse'; // a. Pass function name, and variable number of arguments $diag = call_user_func($funcname, 3, 4); // b. Package arguments as array, pass together with function name $args = array(3, 4); $diag = call_user_func_array($funcname, $args); // ---------------------------- $nums = array(1.4, 3.5, 6.7); // ------------ // Pass-by-value function int_all($arr) { return array_map(create_function('$n', 'return (int) $n;'), $arr); } // Pass-by-reference function trunc_em(&$n) { foreach ($n as &$value) $value = (int) $value; } // ------------ // $nums untouched; $ints is new array $ints = int_all($nums); // $nums updated trunc_em($nums); // @@PLEAC@@_10.2 // Strictly-speaking, PHP is neither lexically [no environment capture] nor // dynamically [no variable shadowing] scoped. A script in which several // functions have been defined has two, entirely separate, scopes: // // * A 'top-level' scope i.e. everything outside each function // // * A 'local scope' within each function; each function is a self-contained // entity and cannot [via conventional means] access variables outside its // local scope. Accessing a variable that has not been locally defined // serves to define it i.e. accessing a variable assumed to be global // sees a local variable of that name defined // // The way 'global' variables are provided is via a predefined array of // variable names, $GLOBALS [it is one of a special set of variables known // as 'superglobals'; such variables *are* accessable in all scopes]. Each // entry in this array is a 'global' variable name, and may be freely // accessed / updated. A more convenient means of accessing such variables // is via the 'global' keyword: one or more variables within a function is // declared 'global', and those names are then taken to refer to entries // in the $GLOBALS array rather than seeing local variables of that name // accessed or defined function some_func() { // Variables declared within a function are local to that function $variable = 'something'; } // ---------------------------- // Top-level declared variables $name = $argv[1]; $age = $argv[2]; $c = fetch_time(); $condition = 0; // ------------ function run_check() { // The globally-declared variable, '$condition', is not accessable within // the function unless it declared as 'global. Had this not been done then // attempts to access, '$condition', would have seen a local variable // of that name declared and updated. Same applies to other variables global $condition, $name, $age, $c; $condition = 1; // ... } function check_x($x) { $y = 'whatever'; // This function only has access to the parameter, '$x', and the locally // declared variable, '$y'. // Whilst 'run_check' has access to several global variables, the current // function does not. For it to access the global variable, '$condition', // it must be declared 'global' run_check(); global $condition; // 'run_check' will have updated, '$condition', and since it has been // declared 'global' here, it is accessable if ($condition) { ; // ... } } // @@PLEAC@@_10.3 // Local scopes are not created in the same way as in Perl [by simply enclosing // within braces]: only via the creation of functions are local scopes created // Doesn't create a local scope; '$myvariable' is created at top-level { $myvariable = 7; } // '$myvariable' is accessable here echo $myvariable . "\n"; // ------------ { $counter = 0; // Local scope created within function, but not within surrounding braces // so: // * '$counter' is actually a top-level variable, so globally accessable // * 'next_counter' has no implict access to '$counter'; must be granted // via 'global' keyword function next_counter() { global $counter; $counter++; } } // ---------------------------- // PHP doesn't, AFAIK, offer an equivalent to Perl's BEGIN block. Similar // behaviour may be obtained by defining a class, and including such code // in its constructor class BEGIN { private $myvariable; function __construct() { $this->myvariable = 5; } function othersub() { echo $this->myvariable . "\n"; } } // ------------ $b = new BEGIN(); $b->othersub(); // ---------------------------- // PHP, like C, supports 'static' local variables, that is, those that upon // first access are initialised, and thence retain their value between function // calls. However, the 'counter' example is better implemented as a class class Counter { private $counter; function __construct($counter_init) { $this->counter = $counter_init; } function next_counter() { $this->counter++; return $this->counter; } function prev_counter() { $this->counter; return $this->counter; } } // ------------ $counter = new Counter(42); echo $counter->next_counter() . "\n"; echo $counter->next_counter() . "\n"; echo $counter->prev_counter() . "\n"; // @@PLEAC@@_10.4 // AFAICT there is no means of obtaining the name of the currently executing // function, or, for that matter, perform any stack / activation record, // inspection. It *is* possible to: // // * Obtain a list of the currently-defined functions ['get_defined_functions'] // * Check whether a specific function exists ['function_exists'] // * Use the 'Reflection API' // // So, to solve this problem would seem to require adopting a convention where // a string representing the function name is passed as an argument, or a local // variable [perhaps called, '$name'] is so set [contrived, and of limited use] function whoami() { $name = 'whoami'; echo "I am: {$name}\n"; } // ------------ whoami(); // @@PLEAC@@_10.5 // In PHP all items exist as 'memory references' [i.e. non-modifiable pointers], // so when passing an item as a function argument, or returning an item from // a function, it is this 'memory reference' that is passed, and *not* the // contents of that item. Should several references to an item exist [e.g. if // passed to a function then at least two such references would exist in // different scopes] they would all be refering to the same copy of the item. // However, if an attempt is made to alter the item is made, a copy is made // and it is the copy that is altered, leaving the original intact. // // The PHP reference mechanism is used to selectively prevent this behaviour, // and ensure that if a change is made to an item that no copy is made, and that // it is the original item that is changed. Importantly, there is no efficiency // gain from passing function parameters using references if the parameter item // is not altered. // A copy of the item referred to by, '$arr', is made, and altered; original // remains intact function array_by_value($arr) { $arr[0] = 7; echo $arr[0] . "\n"; } // No copy is made; original item referred to by, '$arr', is altered function array_by_ref(&$arr) { $arr[0] = 7; echo $arr[0] . "\n"; } // ------------ $arr = array(1, 2, 3); echo $arr[0] . "\n"; // output: 1 array_by_value($arr); // output: 7 echo $arr[0] . "\n"; // output: 1 $arr = array(1, 2, 3); echo $arr[0] . "\n"; // output: 1 array_by_ref($arr); // output: 7 echo $arr[0] . "\n"; // output: 7 // ---------------------------- // Since, 'add_vecpair', does not attempt to alter either, '$x' or '$y', it makes // no difference whether they are 'passed by value', or 'passed by reference' function add_vecpair($x, $y) { $r = array(); $length = count($x); for($i = 0; $i < $length; $i++) $r[$i] = $x[$i] + $y[$i]; return $r; } // ... count($arr1) == count($arr2) || die("usage: add_vecpair ARR1 ARR2\n"); // 'passed by value' $arr3 = add_vecpair($arr1, $arr2); // 'passed by reference' [also possible to override default 'passed by value' // if required] $arr3 = add_vecpair(&$arr1, &$arr2); // @@PLEAC@@_10.6 // PHP can be described as a dynamically typed language because variables serve // as identifiers, and the same variable may refer to data of various types. // As such, the set of arguments passed to a function may vary in type between // calls, as can the type of return value. Where this is likely to occur type // checking should be performed either / both within the function body, and // when obtaining it's return value. As for Perl-style 'return context', I // don't believe it is supported by PHP // Can return any type function mysub() { // ... return 5; // ... return array(5); // ... return '5'; } // Throw away return type [i.e. returns a 'void' type ?] mysub(); // Check return type. Can do via: // * gettype($var) // * is_xxx e.g. is_array($var), is_muneric($var), ... $ret = mysub(); if (is_numeric($ret)) { ; // ... } if (is_array($ret)) { ; // ... } if (is_string($ret)) { ; // ... } // @@PLEAC@@_10.7 // PHP doesn't directly support named / keyword parameters, but these can be // easily mimiced using a class of key / value pairs, and passing a variable // number of arguments class KeyedValue { public $key, $value; public function __construct($key, $value) { $this->key = $key; $this->value = $value; } } function the_func() { foreach (func_get_args() as $arg) { printf("Key: %10s|Value:%10s\n", $arg->key, $arg->value); } } // ---- the_func(new KeyedValue('name', 'Bob'), new KeyedValue('age', 36), new KeyedValue('income', 51000)); // ---------------------------- // Alternatively, an associative array of key / value pairs may be constructed. // With the aid of the 'extract' built-in function, the key part of this array // may be intsntiated to a variable name, thus more closely approximating the // behaviour of true named parameters function the_func($var_array) { extract($var_array); if (isset($name)) printf("Name: %s\n", $name); if (isset($age)) printf("Age: %s\n", $age); if (isset($income)) printf("Income: %s\n", $income); } // ---- the_func(array('name' => 'Bob', 'age' => 36, 'income' => 51000)); // ---------------------------- class RaceTime { public $time, $dim; public function __construct($time, $dim) { $this->time = $time; $this->dim = $dim; } } function the_func($var_array) { extract($var_array); if (isset($start_time)) printf("start: %d - %s\n", $start_time->time, $start_time->dim); if (isset($finish_time)) printf("finish: %d - %s\n", $finish_time->time, $finish_time->dim); if (isset($incr_time)) printf("incr: %d - %s\n", $incr_time->time, $incr_time->dim); } // ---- the_func(array('start_time' => new RaceTime(20, 's'), 'finish_time' => new RaceTime(5, 'm'), 'incr_time' => new RaceTime(3, 'm'))); the_func(array('start_time' => new RaceTime(5, 'm'), 'finish_time' => new RaceTime(30, 'm'))); the_func(array('start_time' => new RaceTime(30, 'm'))); // @@PLEAC@@_10.8 // The 'list' keyword [looks like a function but is actually a special language // construct] may be used to perform multiple assignments from a numerically // indexed array of values, and offers the added bonus of being able to skip // assignment of one, or more, of those values function func() { return array(3, 6, 9); } // ------------ list($a, $b, $c) = array(6, 7, 8); // Provided 'func' returns an numerically-indexed array, the following // multiple assignment will work list($a, $b, $c) = func(); // Any existing variables no longer wanted would need to be 'unset' unset($b); // As above, but second element of return array discarded list($a,,$c) = func(); // ---------------------------- // Care needed to ensure returned array is numerically-indexed list($dev, $ino,,,$uid) = array_slice(array_values(stat($filename)), 0, 13); // @@PLEAC@@_10.9 // Multiple return values are possible via packing a set of values within a // numerically-indexed array and using 'list' to extract them function some_func() { return array(array(1, 2, 3), array('a' => 1, 'b' => 2)); } // ------------ list($arr, $hash) = some_func(); // ---------------------------- function some_func(&$arr, &$hash) { return array($arr, $hash); } // ------------ $arrin = array(1, 2, 3); $hashin = array('a' => 1, 'b' => 2); list($arr, $hash) = some_func($arrin, $hashin); // @@PLEAC@@_10.10 // AFAICT, most of the PHP library functions are designed to return some required // value on success, and FALSE on exit. Whilst it is possible to return NULL, or // one of the recognised 'empty' values [e.g. '' or 0 or an empty array etc], // FALSE actually seems to be the preferred means of indicating failure function a_func() { return FALSE; } a_func() || die("Function failed\n"); if (!a_func()) die("Function failed\n"); // @@PLEAC@@_10.11 // Whether PHP is seen to support prototyping depends on the accepted // definition of this term: // // * Prototyping along the lines used in Ada, Modula X, and even C / C++, // in which a function's interface is declared separately from its // implementation, is *not* supported // // * Prototyping in which, as part of the function definition, parameter // information must be supplied. In PHP a function definition neither // parameter, nor return type, information needs to be supplied, though // it is usual to see a parameter list supplied [indicates the number, // positional order, and optionally, whether a parameter is passed by // reference; no type information is present]. In short, prototyping in // PHP is optional, and limited function func_with_one_arg($arg1) { ; // ... } function func_with_two_arg($arg1, $arg2) { ; // ... } function func_with_three_arg($arg1, $arg2, $arg3) { ; // ... } // The following may be interpreted as meaning a function accepting no // arguments: function func_with_no_arg() { ; // ... } // whilst the following may mean a function taking zero or more arguments function func_with_no_arg_information() { ; // ... } // @@PLEAC@@_10.12 // Unlike in Perl, PHP's 'die' [actually an alias for 'exit'] doesn't throw // an exception, but instead terminates the script, optionally either // returning an integer value to the operating system, or printing a message. // So, the following, does not exhibit the same behaviour as the Perl example die("some message\n"); // Instead, like so many modern languages, PHP implements exception handling // via the 'catch' and 'throw' keywords. Furthermore, a C++ or Java programmer // would find PHP's exception handling facility remarkably similar to those // of their respective languages. A simple, canonical example follows: // Usual to derive new exception classes from the built-in, 'Exception', // class class MyException extends Exception { // ... } // ... try { // ... if ($some_problem_detected) throw new MyException('some message', $some_error_code); // .. } catch (MyException $e) { ; // ... handle the problem ... } // ---------------------------- class FullMoonException extends Exception { // ... } // ... try { // ... if ($some_problem_detected) throw new FullMoonException('...', $full_moon_error_code); // .. } catch (FullMoonException $e) { // ... rethrow the exception - will propagate to higher level ... throw $e; } // @@PLEAC@@_10.13 // Please refer to discussion about PHP scope in section two of this chapter. // Briefly, PHP assumes a variable name within a function to be local unless // it has been specifically declared with the, 'global', keyword, in which // case it refers to a variable in the 'superglobal' array, '$GLOBALS'. Thus, // inadvertant variable name shadowing cannot occur since it is it not possible // to use the same name to refer to both a local and a global variable. If // accessing a global variable care should be taken to not accidentally update // it. The techniques used in this section are simply not required. // *** NOT TRANSLATED *** // @@PLEAC@@_10.14 // In PHP once a function has been defined it remains defined. In other words, // it cannot be undefined / deleted, nor can that particular function name be // reused to reference another function body. Even the lambda-like functions // created via the 'create_function' built-in, cannot be undefined [they exist // until script termination, thus creating too many of these can actually // exhaust memory !]. However, since the latter can be assigned to variables, // the same variable name can be used to reference difference functions [and // when this is done the reference to the previous function is lost (unless // deliberately saved), though the function itself continues to exist]. // // If, however, all that is needed is a simple function aliasing facility, // then just assign the function name to a variable, and execute using the // variable name // Original function function expand() { echo "expand\n"; } // Prove that function exists echo (function_exists('expand') ? 'yes' : 'no') . "\n"; // Use a variable to alias it $grow = 'expand'; // Call function via original name, and variable, respectively expand(); $grow(); // Remove alias variable unset($grow); // ---------------------------- function fred() { echo "fred\n"; } $barney = 'fred'; $barney(); unset($barney); fred(); // ------------ $fred = create_function('', 'echo "fred\n";'); $barney = $fred; $barney(); unset($barney); $fred(); // ---------------------------- function red($text) { return "$text"; } echo red('careful here') . "\n"; // ------------ $colour = 'red'; $$colour = create_function('$text', 'global $colour; return "$text";'); echo $$colour('careful here') . "\n"; unset($$colour); // ---- $colours = split(' ', 'red blue green yellow orange purple violet'); foreach ($colours as $colour) { $$colour = create_function('$text', 'global $colour; return "$text";'); } foreach ($colours as $colour) { echo $$colour("Careful with this $colour, James") . "\n"; } foreach ($colours as $colour) { unset($$colour); } // @@PLEAC@@_10.15 // PHP sports an AUTOLOAD facility that is quite easy to use, but, AFAICT, is geared // towards the detection of unavailable classes rather than for individual functions. // Here is a rudimentary example: function __autoload($classname) { if (!file_exists($classname)) { // Class file does not exist, so handle situation; in this case, // issue error message, and exit program die("File for class: {$classname} not found - aborting\n"); } else { // Class file exists, so load it require_once $classname; } } // ------------ // Attempt to instantiate object of undefined class new UnknownClassObject(); // Execution continues here if class exists // ... // ---------------------------- // It is also possible to perform [quite extensive] introspection on functions, // variables etc, so it is possible to check whether a function exists before // executing it, thus allowing a non-existent functions to be searched for and // loaded from a source file, or perhaps dynamically defined. An example of what // could be described as a custom autoload facility appears below. $colours = array('red', 'blue', 'green', 'yellow', 'orange', 'purple', 'violet'); foreach ($colours as $colour) { $$colour = create_function('$text', 'global $colour; return "$text";'); } // Let's add a new colour to the list array_push($colours, 'chartreuse'); foreach ($colours as $colour) { // Checking whether function is defined if (!function_exists($$colour)) { // Doesn't exist, so dynamically define it $$colour = create_function('$text', 'global $colour; return "$text";'); // Alternatively, if it exists in a source file, 'include' the file: // include 'newcolours.php' } echo $$colour("Careful with this $colour, James") . "\n"; } foreach ($colours as $colour) unset($$colour); // @@PLEAC@@_10.16 // *** Warning *** Whilst PHP *does* allow functions to be defined within other // functions it needs to be clearly understood that these 'inner' functions: // * Do not exist until the outer function is called a first time, at which time // they then remain defined // * Are global in scope, so are accessable outside the function by their name; // the fact that they are nested within another function has, AFAICT, no bearing // on name resolution // * Do not form a closure: the inner function is merely 'parked' within the // outer function, and has no implicit access to the outer function's variables // or other inner functions function outer($arg) { $x = $arg + 35; function inner() { return $x * 19; } // *** wrong *** 'inner' returns 0 * 19, not ($arg + 35) * 19 return $x + inner(); } // ---------------------------- function outer($arg) { $x = $arg + 35; // No implicit access to outer function scope; any required data must be // explicity passed function inner($x) { return $x * 19; } return $x + inner($x); } // ------------ // Equivalent to previously-shown code function inner($x) { return $x * 19; } function outer($arg) { $x = $arg + 35; return $x + inner($x); } // @@PLEAC@@_10.17 // @@INCOMPLETE@@ // @@INCOMPLETE@@ // @@PLEAC@@_16.1 // Run a command and return its results as a string. $output_string = shell_exec('program args'); // Same as above, using backtick operator. $output_string = `program args`; // Run a command and return its results as a list of strings, // one per line. $output_lines = array(); exec('program args', $output_lines); // ----------------------------- // The only way to execute a program without using the shell is to // use pcntl_exec(). However, there is no way to do redirection, so // you can't capture its output. $pid = pcntl_fork(); if ($pid == -1) { die('cannot fork'); } elseif ($pid) { pcntl_waitpid($pid, $status); } else { // Note that pcntl_exec() automatically prepends the program name // to the array of arguments; the program name cannot be spoofed. pcntl_exec($program, array($arg1, $arg2)); } // @@PLEAC@@_16.2 // Run a simple command and retrieve its result code. exec("vi $myfile", $output, $result_code); // ----------------------------- // Use the shell to perform redirection. exec('cmd1 args | cmd2 | cmd3 >outfile'); exec('cmd args outfile 2>errfile'); // ----------------------------- // Run a command, handling its result code or signal. $pid = pcntl_fork(); if ($pid == -1) { die('cannot fork'); } elseif ($pid) { pcntl_waitpid($pid, $status); if (pcntl_wifexited($status)) { $status = pcntl_wexitstatus($status); echo "program exited with status $status\n"; } elseif (pcntl_wifsignaled($status)) { $signal = pcntl_wtermsig($status); echo "program killed by signal $signal\n"; } elseif (pcntl_wifstopped($status)) { $signal = pcntl_wstopsig($status); echo "program stopped by signal $signal\n"; } } else { pcntl_exec($program, $args); } // ----------------------------- // Run a command while blocking interrupt signals. $pid = pcntl_fork(); if ($pid == -1) { die('cannot fork'); } elseif ($pid) { // parent catches INT and berates user declare(ticks = 1); function handle_sigint($signal) { echo "Tsk tsk, no process interruptus\n"; } pcntl_signal(SIGINT, 'handle_sigint'); while (!pcntl_waitpid($pid, $status, WNOHANG)) {} } else { // child ignores INT and does its thing pcntl_signal(SIGINT, SIG_IGN); pcntl_exec('/bin/sleep', array('10')); } // ----------------------------- // Since there is no direct access to execv() and friends, and // pcntl_exec() won't let us supply an alternate program name // in the argument list, there is no way to run a command with // a different name in the process table. // @@PLEAC@@_16.3 // Transfer control to the shell to run another program. pcntl_exec('/bin/sh', array('-c', 'archive *.data')); // Transfer control directly to another program. pcntl_exec('/path/to/archive', array('accounting.data')); // @@PLEAC@@_16.4 // Handle each line in the output of a process. $readme = popen('program arguments', 'r'); while (!feof($readme)) { $line = fgets($readme); if ($line === false) break; // ... } pclose($readme); // ----------------------------- // Write to the input of a process. $writeme = popen('program arguments', 'w'); fwrite($writeme, 'data'); pclose($writeme); // ----------------------------- // Wait for a process to complete. $f = popen('sleep 1000000', 'r'); // child goes to sleep pclose($f); // and parent goes to lala land // ----------------------------- $writeme = popen('program arguments', 'w'); fwrite($writeme, "hello\n"); // program will get hello\n on STDIN pclose($writeme); // program will get EOF on STDIN // ----------------------------- // Output buffering callback that sends output to the pager. function ob_pager($output, $mode) { static $pipe; if ($mode & PHP_OUTPUT_HANDLER_START) { $pager = getenv('PAGER'); if (!$pager) $pager = '/usr/bin/less'; // XXX: might not exist $pipe = popen($pager, 'w'); } fwrite($pipe, $output); if ($mode & PHP_OUTPUT_HANDLER_END) { pclose($pipe); } } // Redirect standard output to the pager. ob_start('ob_pager'); // Do something useful that writes to standard output, then // close the output buffer. // ... ob_end_flush(); // @@PLEAC@@_16.5 // Output buffering: Only display a certain number of lines of output. class Head { function Head($lines=20) { $this->lines = $lines; } function filter($output, $mode) { $result = array(); $newline = ''; if (strlen($output) > 0 && $output[strlen($output) - 1] == "\n") { $newline = "\n"; $output = substr($output, 0, -1); } foreach (explode("\n", $output) as $i => $line) { if ($this->lines > 0) { $this->lines--; $result[] = $line; } } return $result ? implode("\n", $result) . $newline : ''; } } // Output buffering: Prepend line numbers to each line of output. class Number { function Number() { $this->line_number = 0; } function filter($output, $mode) { $result = array(); $newline = ''; if (strlen($output) > 0 && $output[strlen($output) - 1] == "\n") { $newline = "\n"; $output = substr($output, 0, -1); } foreach (explode("\n", $output) as $i => $line) { $this->line_number++; $result[] = $this->line_number . ': ' . $line; } return implode("\n", $result) . $newline; } } // Output buffering: Prepend "> " to each line of output. class Quote { function Quote() { } function filter($output, $mode) { $result = array(); $newline = ''; if (strlen($output) > 0 && $output[strlen($output) - 1] == "\n") { $newline = "\n"; $output = substr($output, 0, -1); } foreach (explode("\n", $output) as $i => $line) { $result[] = "> $line"; } return implode("\n", $result) . $newline; } } // Use arrays as callbacks to register filter methods. ob_start(array(new Head(100), 'filter')); ob_start(array(new Number(), 'filter')); ob_start(array(new Quote(), 'filter')); // Act like /bin/cat. while (!feof(STDIN)) { $line = fgets(STDIN); if ($line === false) break; echo $line; } // Should match number of calls to ob_start(). ob_end_flush(); ob_end_flush(); ob_end_flush(); // @@PLEAC@@_16.6 // Process command-line arguments using fopen(). PHP supports URLs for // filenames as long as the "allow_url_fopen" configuration option is set. // // Valid URL protocols include: // - http://www.myserver.com/myfile.html // - ftp://ftp.myserver.com/myfile.txt // - compress.zlib://myfile.gz // - php://stdin // // See http://www.php.net/manual/en/wrappers.php for details. // $filenames = array_slice($argv, 1); if (!$filenames) $filenames = array('php://stdin'); foreach ($filenames as $filename) { $handle = @fopen($filename, 'r'); if ($handle) { while (!feof($handle)) { $line = fgets($handle); if ($line === false) break; // ... } fclose($handle); } else { die("can't open $filename\n"); } } // @@PLEAC@@_16.7 $output = `cmd 2>&1`; // with backticks // or $ph = popen('cmd 2>&1'); // with an open pipe while (!feof($ph)) { $line = fgets($ph); } // plus a read // ----------------------------- $output = `cmd 2>/dev/null`; // with backticks // or $ph = popen('cmd 2>/dev/null'); // with an open pipe while (!feof($ph)) { $line = fgets($ph); } // plus a read // ----------------------------- $output = `cmd 2>&1 1>/dev/null`; // with backticks // or $ph = popen('cmd 2>&1 1>/dev/null'); // with an open pipe while (!feof($ph)) { $line = fgets($ph); } // plus a read // ----------------------------- $output = `cmd 3>&1 1>&2 2>&3 3>&-`; // with backticks // or $ph = popen('cmd 3>&1 1>&2 2>&3 3>&-|'); // with an open pipe while (!feof($ph)) { $line = fgets($ph); } // plus a read // ----------------------------- exec('program args 1>/tmp/program.stdout 2>/tmp/program.stderr'); // ----------------------------- $output = `cmd 3>&1 1>&2 2>&3 3>&-`; // ----------------------------- $fd3 = $fd1; $fd1 = $fd2; $fd2 = $fd3; $fd3 = null; // ----------------------------- exec('prog args 1>tmpfile 2>&1'); exec('prog args 2>&1 1>tmpfile'); // ----------------------------- // exec('prog args 1>tmpfile 2>&1'); $fd1 = "tmpfile"; // change stdout destination first $fd2 = $fd1; // now point stderr there, too // ----------------------------- // exec('prog args 2>&1 1>tmpfile'); $fd2 = $fd1; // stderr same destination as stdout $fd1 = "tmpfile"; // but change stdout destination // @@PLEAC@@_16.8 // Connect to input and output of a process. $proc = proc_open($program, array(0 => array('pipe', 'r'), 1 => array('pipe', 'w')), $pipes); if (is_resource($proc)) { fwrite($pipes[0], "here's your input\n"); fclose($pipes[0]); echo stream_get_contents($pipes[1]); fclose($pipes[1]); $result_code = proc_close($proc); echo "$result_code\n"; } // ----------------------------- $all = array(); $outlines = array(); $errlines = array(); exec("( $cmd | sed -e 's/^/stdout: /' ) 2>&1", $all); foreach ($all as $line) { $pos = strpos($line, 'stdout: '); if ($pos !== false && $pos == 0) { $outlines[] = substr($line, 8); } else { $errlines[] = $line; } } print("STDOUT:\n"); print_r($outlines); print("\n"); print("STDERR:\n"); print_r($errlines); print("\n"); // @@PLEAC@@_16.9 $proc = proc_open($cmd, array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $pipes); if (is_resource($proc)) { // give end of file to kid, or feed him fclose($pipes[0]); // read till EOF $outlines = array(); while (!feof($pipes[1])) { $line = fgets($pipes[1]); if ($line === false) break; $outlines[] = rtrim($line); } // XXX: block potential if massive $errlines = array(); while (!feof($pipes[2])) { $line = fgets($pipes[2]); if ($line === false) break; $errlines[] = rtrim($line); } fclose($pipes[1]); fclose($pipes[2]); proc_close($proc); print("STDOUT:\n"); print_r($outlines); print("\n"); print("STDERR:\n"); print_r($errlines); print("\n"); } // ----------------------------- // cmd3sel - control all three of kids in, out, and error. $cmd = "grep vt33 /none/such - /etc/termcap"; $proc = proc_open($cmd, array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $pipes); if (is_resource($proc)) { fwrite($pipes[0], "This line has a vt33 lurking in it\n"); fclose($pipes[0]); $readers = array($pipes[1], $pipes[2]); while (stream_select($read=$readers, $write=null, $except=null, 0, 200000) > 0) { foreach ($read as $stream) { $line = fgets($stream); if ($line !== false) { if ($stream === $pipes[1]) { print "STDOUT: $line"; } else { print "STDERR: $line"; } } if (feof($stream)) { $readers = array_diff($readers, array($stream)); } } } fclose($pipes[1]); fclose($pipes[2]); proc_close($proc); } // @@PLEAC@@_16.10 // PHP supports fork/exec/wait but not pipe. However, it does // support socketpair, which can do everything pipes can as well // as bidirectional communication. The original recipes have been // modified here to use socketpair only. // ----------------------------- // pipe1 - use socketpair and fork so parent can send to child $sockets = array(); if (!socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $sockets)) { die(socket_strerror(socket_last_error())); } list($reader, $writer) = $sockets; $pid = pcntl_fork(); if ($pid == -1) { die('cannot fork'); } elseif ($pid) { socket_close($reader); $line = sprintf("Parent Pid %d is sending this\n", getmypid()); if (!socket_write($writer, $line, strlen($line))) { socket_close($writer); die(socket_strerror(socket_last_error())); } socket_close($writer); pcntl_waitpid($pid, $status); } else { socket_close($writer); $line = socket_read($reader, 1024, PHP_NORMAL_READ); printf("Child Pid %d just read this: `%s'\n", getmypid(), rtrim($line)); socket_close($reader); // this will happen anyway exit(0); } // ----------------------------- // pipe2 - use socketpair and fork so child can send to parent $sockets = array(); if (!socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $sockets)) { die(socket_strerror(socket_last_error())); } list($reader, $writer) = $sockets; $pid = pcntl_fork(); if ($pid == -1) { die('cannot fork'); } elseif ($pid) { socket_close($writer); $line = socket_read($reader, 1024, PHP_NORMAL_READ); printf("Parent Pid %d just read this: `%s'\n", getmypid(), rtrim($line)); socket_close($reader); pcntl_waitpid($pid, $status); } else { socket_close($reader); $line = sprintf("Child Pid %d is sending this\n", getmypid()); if (!socket_write($writer, $line, strlen($line))) { socket_close($writer); die(socket_strerror(socket_last_error())); } socket_close($writer); // this will happen anyway exit(0); } // ----------------------------- // pipe3 and pipe4 demonstrate the use of perl's "forking open" // feature to reimplement pipe1 and pipe2. pipe5 uses two pipes // to simulate socketpair. Since PHP supports socketpair but not // pipe, and does not have a "forking open" feature, these // examples are skipped here. // ----------------------------- // pipe6 - bidirectional communication using socketpair $sockets = array(); if (!socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $sockets)) { die(socket_strerror(socket_last_error())); } list($child, $parent) = $sockets; $pid = pcntl_fork(); if ($pid == -1) { die('cannot fork'); } elseif ($pid) { socket_close($parent); $line = sprintf("Parent Pid %d is sending this\n", getmypid()); if (!socket_write($child, $line, strlen($line))) { socket_close($child); die(socket_strerror(socket_last_error())); } $line = socket_read($child, 1024, PHP_NORMAL_READ); printf("Parent Pid %d just read this: `%s'\n", getmypid(), rtrim($line)); socket_close($child); pcntl_waitpid($pid, $status); } else { socket_close($child); $line = socket_read($parent, 1024, PHP_NORMAL_READ); printf("Child Pid %d just read this: `%s'\n", getmypid(), rtrim($line)); $line = sprintf("Child Pid %d is sending this\n", getmypid()); if (!socket_write($parent, $line, strlen($line))) { socket_close($parent); die(socket_strerror(socket_last_error())); } socket_close($parent); exit(0); } // @@PLEAC@@_16.11 // ----------------------------- // % mkfifo /path/to/named.pipe // ----------------------------- $fifo = fopen('/path/to/named.pipe', 'r'); if ($fifo !== false) { while (!feof($fifo)) { $line = fgets($fifo); if ($line === false) break; echo "Got: $line"; } fclose($fifo); } else { die('could not open fifo for read'); } // ----------------------------- $fifo = fopen('/path/to/named.pipe', 'w'); if ($fifo !== false) { fwrite($fifo, "Smoke this.\n"); fclose($fifo); } else { die('could not open fifo for write'); } // ----------------------------- // % mkfifo ~/.plan # isn't this everywhere yet? // % mknod ~/.plan p # in case you don't have mkfifo // ----------------------------- // dateplan - place current date and time in .plan file while (true) { $home = getenv('HOME'); $fifo = fopen("$home/.plan", 'w'); if ($fifo === false) { die("Couldn't open $home/.plan for writing.\n"); } fwrite($fifo, 'The current time is ' . strftime('%a, %d %b %Y %H:%M:%S %z') . "\n"); fclose($fifo); sleep(1); } // ----------------------------- // fifolog - read and record log msgs from fifo $fifo = null; declare(ticks = 1); function handle_alarm($signal) { global $fifo; if ($fifo) fclose($fifo); // move on to the next queued process } pcntl_signal(SIGALRM, 'handle_alarm'); while (true) { pcntl_alarm(0); // turn off alarm for blocking open $fifo = fopen('/tmp/log', 'r'); if ($fifo === false) { die("can't open /tmp/log"); } pcntl_alarm(1); // you have 1 second to log $service = fgets($fifo); if ($service === false) continue; // interrupt or nothing logged $service = rtrim($service); $message = fgets($fifo); if ($message === false) continue; // interrupt or nothing logged $message = rtrim($message); pcntl_alarm(0); // turn off alarms for message processing if ($service == 'http') { // ignoring } elseif ($service == 'login') { // log to /var/log/login $log = fopen('/var/log/login', 'a'); if ($log !== false) { fwrite($log, strftime('%a, %d %b %Y %H:%M:%S %z') . " $service $message\n"); fclose($log); } else { trigger_error("Couldn't log $service $message to /var/log/login\n", E_USER_WARNING); } } } // @@PLEAC@@_16.12 // sharetest - test shared variables across forks $SHM_KEY = ftok(__FILE__, chr(1)); $handle = sem_get($SHM_KEY); $buffer = shm_attach($handle, 1024); // The original recipe has an INT signal handler here. However, it // causes erratic behavior with PHP, and PHP seems to do the right // thing without it. for ($i = 0; $i < 10; $i++) { $child = pcntl_fork(); if ($child == -1) { die('cannot fork'); } elseif ($child) { $kids[] = $child; // in case we care about their pids } else { squabble(); exit(); } } while (true) { print 'Buffer is ' . shm_get_var($buffer, 1) . "\n"; sleep(1); } die('Not reached'); function squabble() { global $handle; global $buffer; $i = 0; $pid = getmypid(); while (true) { if (preg_match("/^$pid\\b/", shm_get_var($buffer, 1))) continue; sem_acquire($handle); $i++; shm_put_var($buffer, 1, "$pid $i"); sem_release($handle); } } // Buffer is 14357 1 // Buffer is 14355 3 // Buffer is 14355 4 // Buffer is 14354 5 // Buffer is 14353 6 // Buffer is 14351 8 // Buffer is 14351 9 // Buffer is 14350 10 // Buffer is 14348 11 // Buffer is 14348 12 // Buffer is 14357 10 // Buffer is 14357 11 // Buffer is 14355 13 // ... // @@PLEAC@@_16.13 // Available signal constants % php -r 'print_r(get_defined_constants());' | grep '\[SIG' | grep -v _ [SIGHUP] => 1 [SIGINT] => 2 [SIGQUIT] => 3 [SIGILL] => 4 [SIGTRAP] => 5 [SIGABRT] => 6 [SIGIOT] => 6 [SIGBUS] => 7 [SIGFPE] => 8 [SIGKILL] => 9 [SIGUSR1] => 10 [SIGSEGV] => 11 [SIGUSR2] => 12 [SIGPIPE] => 13 [SIGALRM] => 14 [SIGTERM] => 15 [SIGSTKFLT] => 16 [SIGCLD] => 17 [SIGCHLD] => 17 [SIGCONT] => 18 [SIGSTOP] => 19 [SIGTSTP] => 20 [SIGTTIN] => 21 [SIGTTOU] => 22 [SIGURG] => 23 [SIGXCPU] => 24 [SIGXFSZ] => 25 [SIGVTALRM] => 26 [SIGPROF] => 27 [SIGWINCH] => 28 [SIGPOLL] => 29 [SIGIO] => 29 [SIGPWR] => 30 [SIGSYS] => 31 [SIGBABY] => 31 // Predefined signal handler constants % php -r 'print_r(get_defined_constants());' | grep '\[SIG' | grep _ [SIG_IGN] => 1 [SIG_DFL] => 0 [SIG_ERR] => -1 // @@PLEAC@@_16.14 // send pid a signal 9 posix_kill($pid, 9); // send whole job a signal 1 posix_kill($pgrp, -1); // send myself a SIGUSR1 posix_kill(getmypid(), SIGUSR1); // send a SIGHUP to processes in pids foreach ($pids as $pid) posix_kill($pid, SIGHUP); // ----------------------------- // Use kill with pseudo-signal 0 to see if process is alive. if (posix_kill($minion, 0)) { echo "$minion is alive!\n"; } else { echo "$minion is deceased.\n"; } // @@PLEAC@@_16.15 // call got_sig_quit for every SIGQUIT pcntl_signal(SIGQUIT, 'got_sig_quit'); // call got_sig_pipe for every SIGPIPE pcntl_signal(SIGPIPE, 'got_sig_pipe'); // increment ouch for every SIGINT function got_sig_int($signal) { global $ouch; $ouch++; } pcntl_signal(SIGINT, 'got_sig_int'); // ignore the signal INT pcntl_signal(SIGINT, SIG_IGN); // restore default STOP signal handling pcntl_signal(SIGSTOP, SIG_DFL); // @@PLEAC@@_16.16 // the signal handler function ding($signal) { fwrite(STDERR, "\x07Enter your name!\n"); } // prompt for name, overriding SIGINT function get_name() { declare(ticks = 1); pcntl_signal(SIGINT, 'ding'); echo "Kindly Stranger, please enter your name: "; while (!@stream_select($read=array(STDIN), $write=null, $except=null, 1)) { // allow signals to be observed } $name = fgets(STDIN); // Since pcntl_signal() doesn't return the old signal handler, the // best we can do here is set it back to the default behavior. pcntl_signal(SIGINT, SIG_DFL); return $name; } // @@PLEAC@@_16.17 function got_int($signal) { pcntl_signal(SIGINT, 'got_int'); // but not for SIGCHLD! // ... } pcntl_signal(SIGINT, 'got_int'); // ----------------------------- declare(ticks = 1); $interrupted = false; function got_int($signal) { global $interrupted; $interrupted = true; // The third argument to pcntl_signal() determines if system calls // should be restarted after a signal. It defaults to true. pcntl_signal(SIGINT, 'got_int', false); // or SIG_IGN } pcntl_signal(SIGINT, 'got_int', false); // ... long-running code that you don't want to restart if ($interrupted) { // deal with the signal } // @@PLEAC@@_16.18 // ignore signal INT pcntl_signal(SIGINT, SIG_IGN); // install signal handler declare(ticks = 1); function tsktsk($signal) { fwrite(STDERR, "\x07The long habit of living indisposeth us for dying."); pcntl_signal(SIGINT, 'tsktsk'); } pcntl_signal(SIGINT, 'tsktsk'); // @@PLEAC@@_16.19 pcntl_signal(SIGCHLD, SIG_IGN); // ----------------------------- declare(ticks = 1); function reaper($signal) { $pid = pcntl_waitpid(-1, $status, WNOHANG); if ($pid > 0) { // ... reaper($signal); } // install *after* calling waitpid pcntl_signal(SIGCHLD, 'reaper'); } pcntl_signal(SIGCHLD, 'reaper'); // ----------------------------- declare(ticks = 1); function reaper($signal) { $pid = pcntl_waitpid(-1, $status, WNOHANG); if ($pid == -1) { // No child waiting. Ignore it. } else { if (pcntl_wifexited($signal)) { echo "Process $pid exited.\n"; } else { echo "False alarm on $pid\n"; } reaper($signal); } pcntl_signal(SIGCHLD, 'reaper'); } pcntl_signal(SIGCHLD, 'reaper'); // @@PLEAC@@_16.20 // PHP does not support sigprocmask(). // @@PLEAC@@_16.21 declare(ticks = 1); $aborted = false; function handle_alarm($signal) { global $aborted; $aborted = true; } pcntl_signal(SIGALRM, 'handle_alarm'); pcntl_alarm(3600); // long-time operations here pcntl_alarm(0); if ($aborted) { // timed out - do what you will here }