├── .bowerrc ├── .editorconfig ├── .gitignore ├── .jshintrc ├── Gruntfile.coffee ├── README.md ├── assets ├── img │ ├── Design-Patterns-Elements-of-Reusable-Object-Oriented-Software.jpg │ ├── POEAA.jpg │ ├── awesome-moose.gif │ ├── barbara-liskov.jpg │ ├── caution-sign.svg │ ├── chuck-testa.png │ ├── deer.png │ ├── doctrine.svg │ ├── excel-saga-warning.png │ ├── for-the-glory-of-satan.jpg │ ├── gh.svg │ ├── github-white.png │ ├── github.svg │ ├── i-have-no-idea-what-i-m-doing.png │ ├── lazy-loading.png │ ├── nooooo-darth-vader.jpg │ ├── nope.png │ ├── ocramius.gif │ ├── roave-logo.png │ ├── so-what-i-have-a-tie.jpg │ ├── twitter.svg │ └── zf-logo.svg └── style.css ├── bower.json ├── index.html ├── index.tpl ├── js └── loadhtmlslides.js ├── package.json └── slides ├── index.md └── list.json /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | *.log 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "bitwise": true, 4 | "camelcase": true, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "immed": true, 8 | "indent": 4, 9 | "latedef": true, 10 | "newcap": true, 11 | "noarg": true, 12 | "quotmark": "single", 13 | "regexp": true, 14 | "undef": true, 15 | "unused": true, 16 | "strict": true, 17 | "trailing": true, 18 | "smarttabs": true, 19 | "white": true 20 | } 21 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | # Generated on 2013-10-28 using generator-reveal 0.2.0 2 | module.exports = (grunt) -> 3 | 4 | grunt.initConfig 5 | 6 | watch: 7 | 8 | livereload: 9 | options: 10 | livereload: true 11 | files: [ 12 | 'index.html', 13 | 'slides/*.md', 14 | 'slides/*.html', 15 | 'js/*.js' 16 | ] 17 | 18 | coffeelint: 19 | files: ['Gruntfile.coffee'] 20 | tasks: ['coffeelint'] 21 | 22 | jshint: 23 | files: ['js/*.js'] 24 | tasks: ['jshint'] 25 | 26 | connect: 27 | 28 | livereload: 29 | options: 30 | port: 9000 31 | # Change hostname to '0.0.0.0' to access 32 | # the server from outside. 33 | hostname: 'localhost' 34 | base: '.' 35 | open: true 36 | livereload: true 37 | 38 | coffeelint: 39 | 40 | options: 41 | indentation: 42 | value: 4 43 | 44 | files: ['Gruntfile.coffee'] 45 | 46 | jshint: 47 | 48 | options: 49 | jshintrc: '.jshintrc' 50 | 51 | files: ['js/*.js'] 52 | 53 | # Load all grunt tasks. 54 | require('load-grunt-tasks')(grunt) 55 | 56 | grunt.registerTask 'server', 57 | 'Run presentation locally and start watch process (living document).', [ 58 | 'connect:livereload', 59 | 'watch' 60 | ] 61 | 62 | # Define default task. 63 | grunt.registerTask 'default', [ 64 | 'coffeelint', 65 | 'jshint', 66 | 'connect:livereload', 67 | 'watch' 68 | ] 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Proxy Pattern in PHP 2 | ==================== 3 | 4 | This repository contains the source code for the slides that are 5 | live at [http://ocramius.github.io/presentations/proxy-pattern-in-php/](http://ocramius.github.io/presentations/proxy-pattern-in-php/) 6 | -------------------------------------------------------------------------------- /assets/img/Design-Patterns-Elements-of-Reusable-Object-Oriented-Software.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/Design-Patterns-Elements-of-Reusable-Object-Oriented-Software.jpg -------------------------------------------------------------------------------- /assets/img/POEAA.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/POEAA.jpg -------------------------------------------------------------------------------- /assets/img/awesome-moose.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/awesome-moose.gif -------------------------------------------------------------------------------- /assets/img/barbara-liskov.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/barbara-liskov.jpg -------------------------------------------------------------------------------- /assets/img/caution-sign.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Caution Logo 4 | 5 | Layer 1 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /assets/img/chuck-testa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/chuck-testa.png -------------------------------------------------------------------------------- /assets/img/deer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/deer.png -------------------------------------------------------------------------------- /assets/img/doctrine.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 44 | 48 | 52 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 71 | 84 | 90 | 103 | 104 | 109 | 115 | 128 | 141 | 154 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /assets/img/excel-saga-warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/excel-saga-warning.png -------------------------------------------------------------------------------- /assets/img/for-the-glory-of-satan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/for-the-glory-of-satan.jpg -------------------------------------------------------------------------------- /assets/img/gh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Created by potrace 1.10, written by Peter Selinger 2001-2011 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /assets/img/github-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/github-white.png -------------------------------------------------------------------------------- /assets/img/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | image/svg+xml 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /assets/img/i-have-no-idea-what-i-m-doing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/i-have-no-idea-what-i-m-doing.png -------------------------------------------------------------------------------- /assets/img/lazy-loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/lazy-loading.png -------------------------------------------------------------------------------- /assets/img/nooooo-darth-vader.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/nooooo-darth-vader.jpg -------------------------------------------------------------------------------- /assets/img/nope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/nope.png -------------------------------------------------------------------------------- /assets/img/ocramius.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/ocramius.gif -------------------------------------------------------------------------------- /assets/img/roave-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/roave-logo.png -------------------------------------------------------------------------------- /assets/img/so-what-i-have-a-tie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ocramius/proxy-pattern-in-php/875f0edd52a9ac2a5c4287e8b6f55fe2037bcbbf/assets/img/so-what-i-have-a-tie.jpg -------------------------------------------------------------------------------- /assets/img/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/img/zf-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | .reveal section img { 2 | border: 0; 3 | background: none; 4 | box-shadow: 0 0 #000; 5 | } 6 | 7 | .php-color { 8 | color: #6C7EB7; 9 | } 10 | 11 | .proxy-color { 12 | color: #FC6A31; 13 | } 14 | 15 | .zf-color { 16 | color: #68B604; 17 | } 18 | 19 | .doctrine-color { 20 | color: #FC6A31; 21 | } 22 | 23 | .soothe .reveal .state-background { 24 | background: rgba(255, 255, 255, 0.6); 25 | } 26 | 27 | .pros-line:before, .cons-line:before { 28 | content: ' '; 29 | position: absolute; 30 | width: 0; 31 | height: 0; 32 | left: -40px; 33 | border: 12px solid transparent; 34 | } 35 | 36 | .pros-line:before { 37 | border-bottom-width: 22px; 38 | border-bottom-color: green; 39 | } 40 | 41 | .cons-line:before { 42 | border-top-width: 22px; 43 | border-top-color: red; 44 | } -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "the-proxy-pattern-in-php", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "reveal.js": "master" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OOP Proxies in PHP 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 22 | 23 | 24 | 64 | 65 | 66 |
67 |
68 | 69 |
70 |
71 |

72 | Hello! 73 |

74 |
75 |
76 |

77 | Marco Pivetta 78 | Marco Pivetta 79 |

80 |
81 |
82 |

83 | Marco Pivetta 84 | Ocramius 85 |

86 |
87 |
88 | Roave 89 |

90 | 91 | Evan Coury (EvanDotPro) 92 | 93 | 94 | Justin Martin (FrozenFire) 95 | 96 | 97 | Ben Scholzen (DASPRiD) 98 | 99 | 100 | Marco Pivetta (Ocramius) 101 | 102 | 103 | Rob Allen (Akrabat), Nineteen Feet) 104 | 105 | 106 | Nigel Lundsten (nlundsten) 107 | 108 | 109 | Aleksey Khudyakov (Xerkus) 110 | 111 | 112 | Gary Hockin (Spabby / GeeH) 113 | 114 | Priscilla Hubbard 115 | Chloe 116 |

117 |

118 | We're awesome! 119 |

120 |

(And we love <marquee/>)

121 |
122 |
123 |

Awesome:

124 | Definition of Awesome 125 |
126 |
127 |

128 | Doctrine Project 134 | octrine team 135 |

136 |
137 |
138 |

139 | Zend Framework 2 145 | contributor 146 |

147 |
148 |
149 |

150 | Ocramius on Github 156 | Ocramius on Twitter 162 | Ocramius 163 |

164 |
165 | 166 |
167 |
168 |

Current projects

169 |

170 | ProxyManager, 171 | BjyAuthorize, 172 | AssetManager, 173 | ZeffMu, 174 | ZfrRest, 175 | OcraDiCompiler, 176 | OcraServiceManager, 177 | OcraCachedViewResolver, 178 | DoctrineModule, 179 | DoctrineORMModule, 180 | DoctrineMongoODMModule, 181 | VersionEyeModule 182 |

183 |
184 |
185 |
186 |

WHY?

187 |
188 |
189 | For the glory of satan, of course! 190 |
191 | 192 |
193 |

194 | The Proxy Pattern 195 |
196 | in PHP 197 |

198 |
199 | 200 |
201 |

What is a Proxy?

202 |
203 |
204 |

205 | A proxy 206 | is a class functioning as an interface to 207 | something else 208 |

209 | 210 |

(carefully pasted from wikipedia)

211 |
212 |
213 |

Simple put

214 |
215 |
216 | Deer 217 |
218 |
219 | Nope 220 |
221 |
222 | Chuck Testa! 223 |
224 | 225 | 226 |
227 |

A Proxy implements a Subject's interface

228 |
229 |
230 |

231 | A proxy replaces 232 | subject thanks to the 233 | 234 | LSP 235 | 236 |

237 |
238 | 239 |
240 | A Proxy implements the same interface as of a real subject 245 |
246 |
247 |

Client consumes SubjectInterface

248 |
249 | 250 |
251 | A Client uses the SubjectInterface 256 |
257 |
258 |

Client consumes RealSubject

259 |
260 | 261 |
262 | A Client uses the RealSubject 267 |
268 |
269 |

Client consumes SubjectProxy

270 |
271 | 272 |
273 | A Client uses the SubjectProxy 278 |
279 | 280 |
281 |

Simplified:

282 |

283 | SubjectProxy extending RealSubject 284 |

285 |
286 | 287 |
288 |
class SubjectProxy extends RealSubject { /* ... */ }
289 |

290 | ProxySubject extends the RealSubject 295 |

296 | 297 |

Though you should use interfaces!

298 |
299 | 300 |
301 | So what? I have a tie 305 |
306 | 307 |
308 |

What do Proxies do?

309 |
310 | 311 |
312 |

Some definitions here:

313 | PoEAA 314 |
315 | 316 |
317 |

And here:

318 | Design Patterns Elements of Reusable Object Oriented Software 322 |
323 | 324 |
325 |

And a lot of confusion over the internets :(

326 |
327 | 328 |
329 |

From the GoF

330 |
331 | 332 |
333 |

334 | Remote proxy 335 |

336 |

337 | Virtual proxy 338 |

339 |

340 | Protection proxy 341 |

342 |

343 | Smart reference 344 |

345 |
346 | 347 |
348 |

I'm a software architect

349 | I have no idea what I'm doing 354 |
355 | 356 |
357 |

Let's get practical!

358 |
359 | 360 |
361 |

Remote Proxy

362 |
363 | 364 |
365 | Remote Object Proxy UML 370 |
371 | 372 |
373 | Remote Object Proxy UML 378 |
379 | 380 |
381 |

Remote Object example

382 |
383 | 384 |
385 |
class Tweet {
 386 |     protected $data;
 387 | 
 388 |     public function __construct(array $data) {
 389 |         $this->data = $data;
 390 |     }
 391 | 
 392 |     public function getText() {
 393 |         return $this->data['text'];
 394 |     }
 395 | }
396 |
397 | 398 |
399 |
class TweetProxy extends Tweet {
 400 |     protected $api;
 401 |     protected $id;
 402 | 
 403 |     public function __construct(TwitterApi $api, $id) {
 404 |         $this->api = $api;
 405 |         $this->id  = $id;
 406 |     }
 407 | 
 408 |     public function getText() {
 409 |         return $this->api->getTweetText($this->id);
 410 |     }
 411 | }
412 |
413 | 414 |
415 |
$tweet = new Tweet(array('text' => 'Proxies in PHP!'));
 416 | var_dump($tweet->getText()); // Proxies in PHP!
 417 | 
 418 | $api = new TwitterApi(/* yadda */);
 419 | 
 420 | $remoteTweet = new TweetProxy($api, 280643708968386560);
 421 | var_dump($remoteTweet->getText()); // Tweet text!
 422 | 
 423 | $remoteTweet = new TweetProxy($api, 280643708968386561);
 424 | var_dump($remoteTweet->getText()); // Another text!
425 |
426 | 427 |
428 |

remote object pros/cons

429 |

Share objects across multiple systems

430 |

No local memory usage

431 |

Can act as an adapter for a completely different remote object

432 |

Complex setup

433 |

Fails on adapter failure

434 |

As slow as the protocol

435 |
436 | 437 |
438 |

Lazy Loading

439 | 440 | Lazy Loading 441 |
442 | 443 |
444 |

For very expensive objects:

445 |
    446 |
  • DB Connections
  • 447 |
  • Files (I/O in general)
  • 448 |
  • Large objects (memory)
  • 449 |
  • Objects with long instantiation time (hashes/parsed values/etc)
  • 450 |
451 |

(A pattern, not a proxy type)

452 |
453 |
454 |

Lazy Loading example

455 |
456 |
457 |
class Customer {
 458 |     public function __construct(DbConnection $db, $id) {
 459 |         $this->db = $db;
 460 |         $this->id = $id;
 461 |     }
 462 | 
 463 |     public function getName() {
 464 |         $this->init();
 465 |         return $this->data['name'];
 466 |     }
 467 | 
 468 |     private function init() {
 469 |         if (!isset($this->data)) {
 470 |             $this->data = $this->db->load($this->id);
 471 |         }
 472 |     }
 473 | }
474 | 475 |
476 |
477 |

Problems with Lazy Loading

478 | 479 |

Instantiation logic mixed with class logic

480 |

Dependency to loader objects in the object

481 |

Not really optimized, and unreadable if optimized

482 |

Harder to test

483 |
484 |
485 |

Lazy loading via Proxies

486 | 487 |

488 | Virtual Proxy 489 |
490 | An object that looks like the RealSubject, but just holds a lazy reference to it. 491 |

492 |

493 | Ghost Object 494 |
495 | An object that looks like the RealSubject, but with all properties not being 496 | set and being lazy loaded. 497 |

498 | 506 |
507 | 508 |
509 |

Virtual Proxy

510 |
511 | 512 |
513 |
class Image {
 514 |     protected $image;
 515 |     public function __construct($path) {
 516 |         $this->image = imagecreatefromjpeg($path);
 517 |     }
 518 | 
 519 |     public function getSize() {
 520 |         return array(
 521 |             imagesx($this->image),
 522 |             imagesy($this->image),
 523 |         );
 524 |     }
 525 | }
526 |
527 | 528 |
529 |
class ImageVirtualProxy extends Image {
 530 |     protected $wrapped;
 531 |     public function __construct($path) {
 532 |         $this->path = $path;
 533 |     }
 534 | 
 535 |     public function getSize() {
 536 |         $this->init();
 537 |         return $this->wrapped->getSize();
 538 |     }
 539 | 
 540 |     private function init() {
 541 |         if ( ! $this->wrapped) {
 542 |             $this->wrapped = new Image($this->path);
 543 |         }
 544 |     }
 545 | }
546 |
547 | 548 |
549 |
$img1 = new ImageProxy('/path/to/image1.jpg');
 550 | var_dump(memory_get_usage()); // ~200Kb
 551 | $img2 = new ImageProxy('/path/to/image2.jpg');
 552 | var_dump(memory_get_usage()); // ~200Kb
 553 | $img3 = new ImageProxy('/path/to/image3.jpg');
 554 | var_dump(memory_get_usage()); // ~200Kb
 555 | 
 556 | $size1 = $img1->getSize();
 557 | var_dump(memory_get_usage()); // ~4Mb
 558 | $size2 = $img2->getSize();
 559 | var_dump(memory_get_usage()); // ~8Mb
560 |
561 | 562 |
563 |

Virtual Proxy pros/cons

564 |

Abstracts initialization logic away

565 |

Improved memory/performance impact

566 |

Low overhead

567 |

Easy to implement

568 |

Not optimal for data that is always loaded

569 |

Lazy loading means lazy failing

570 |

Actually, not the same object as RealSubject (different identity)

571 |
572 | 573 |
574 |

Ghost Object

575 |

576 | An object whose properties are the same of the proxied object, but null. 577 |

578 |

579 | Accessing any method causes loading of the properties. 580 |

581 |

Useful when the identity of the object has to be preserved

582 |

583 | Doctrine Proxies 584 | are generated this way. 585 |

586 |
587 | 588 |
589 |

Ghost Object (simplified) example

590 |
591 |
592 |
class ImageGhostProxy extends Image {
 593 |     protected $initialized = false;
 594 |     public function __construct($path) {
 595 |         $this->path = $path;
 596 |     }
 597 |     public function getSize() {
 598 |         $this->init();
 599 |         return parent::getSize();
 600 |     }
 601 |     private function init() {
 602 |         if ( ! $this->initialized) {
 603 |             $image             = new Image($this->path);
 604 |             $this->image       = $image->image;
 605 |             $this->initialized = true;
 606 |         }
 607 |     }
 608 | }
609 |
610 | 611 |
612 |

Ghost Object pros/cons

613 |

Same identity as RealSubject

614 |

Abstracts initialization logic away

615 |

Improved memory/performance impact

616 |

Low overhead

617 |

Harder to implement

618 |

May require reflection to access parent class's properties

619 |

Not optimal for data that is always loaded

620 |

Lazy loading means lazy failing

621 |
622 | 623 | 624 | 625 | 626 |
627 |

Protection proxy

628 |

629 | A Protection Proxy comes into play when you want to transparently 630 | limit access to an API through a set of rules (ACL/limits/custom logic). 631 |

632 |

It actually is a decorator to the proxied RealSubject.

633 |
634 | 635 |
636 |
class APIProtectionProxy extends API {
 637 |     protected $count = 0;
 638 |     public function __construct(API $api, $limit) {
 639 |         $this->api = $api; $this->limit = $limit;
 640 |     }
 641 | 
 642 |     public function doStuff() {
 643 |         $this->count();
 644 |         return $this->api->doStuff();
 645 |     }
 646 | 
 647 |     private function count() {
 648 |         if (++$this->count > $this->limit) {
 649 |             throw new RemoteApiLimit('STAHP!');
 650 |         }
 651 |     }
 652 | }
653 |
654 | 655 |
656 |
$api = new APIProtectionProxy(new API(/* ... */), 50);
 657 | 
 658 | while (true) {
 659 |     $api->doStuff(); // RemoteApiLimit exception!
 660 | }
661 |
662 | 663 |
664 |

Protection Proxy pros/cons

665 |

Limiting access to an API does not require changes in the API

666 |

Modifies proxied object behavior

667 |
668 | 669 |
670 |

Smart reference

671 |

672 | A Smart reference executes additional operations when the 673 | RealSubject is accessed 674 |

675 |

676 | Smart reference is actually DecoratorPattern 677 |

678 |

Good for AOP

679 |
680 |
681 |

Smart reference use cases

682 |
    683 |
  • Caching slow method execution
  • 684 |
  • Logging
  • 685 |
  • Event triggering
  • 686 |
  • Mocking
  • 687 |
  • AOP in general
  • 688 |
689 |
690 | 691 |
692 |

Smart reference example

693 |

Let's cache a complex/slow method call

694 |
695 | 696 |
697 |
    /** @AOP\Cache(ttl=3600) */
 698 |     public function doHeavyStuff() {
 699 |         // [...]
 700 |     }
701 |

Could become:

702 |
class APICachingProxy extends API {
 703 |     public function __construct(API $api, Cache $cache) {
 704 |         $this->api = $api; $this->cache = $cache;
 705 |     }
 706 | 
 707 |     public function doHeavyStuff() {
 708 |         if ($cached = $this->cache->get('doHeavyStuff')) {
 709 |             return $cached;
 710 |         }
 711 | 
 712 |         $result = $this->api->doHeavyStuff();
 713 | 
 714 |         $this->cache->set('doHeavyStuff', $result, 3600);
 715 | 
 716 |         return $result;
 717 |     }
 718 | }
719 |
720 | 721 |
722 |

Implementing a Proxy in PHP

723 |
724 | 725 |
726 |

Poor coder's proxy implementation:

727 |
class MyProxy {
 728 |     // ...
 729 | 
 730 |     public function __call($method, $args) {
 731 |         return call_user_func_array(
 732 |             $this->someWrappedStuff,
 733 |             $args
 734 |         );
 735 |     }
 736 | }
737 |
738 | 739 |
740 | don't do it! 741 |
742 | 743 |
744 |
class BankAccount { /* ... */ }
745 |
function pay(BankAccount $account) { /* ... */ }
746 |
class PoorProxy {
 747 |     public function __construct($wrapped) {
 748 |         $this->wrapped = $wrapped;
 749 |     }
 750 | 
 751 |     public function __call($method, $args) {
 752 |         return call_user_func_array(
 753 |             $this->wrapped,
 754 |             $args
 755 |         );
 756 |     }
 757 | }
758 |
$account = new PoorProxy(new BankAccount());
 759 | 
 760 | pay($account); // KABOOM!
761 |
762 | 763 |
764 |

Implementing a
(generic) Proxy

765 |
766 | 767 |
768 |

1. MUST respect the LSP!

769 |
770 | 771 |
772 |

2. Not so trivial!

773 |
774 | 775 |
776 |

PHP is a maze!

777 | 778 |
    779 |
  • magic getters
  • 780 |
  • magic setters
  • 781 |
  • serialization
  • 782 |
  • cloning
  • 783 |
  • public properties
  • 784 |
785 |
786 | 787 |
788 |

A lot of stuff to consider!

789 |
790 | 791 |
792 |

Proxies are NOT nice:

793 |
<?php
 794 | namespace Proxy\__PM__;
 795 | 
 796 | class A extends \A
 797 | {
 798 |     private $valueHolder;
 799 |     private $initializer;
 800 | 
 801 |     public function getFoo()
 802 |     {
 803 |         $this->initializer && $this->initializer->__invoke($this->valueHolder, $this, 'getFoo', array(), $this->initializer);
 804 | 
 805 |         return $this->valueHolder->getFoo();
 806 |     }
 807 | 
 808 |     public function __construct($initializer)
 809 |     {
 810 |         unset($this->foo);
 811 | 
 812 |         $this->initializer = $initializer;
 813 |     }
 814 | 
 815 |     public function __get($name)
 816 |     {
 817 |         $this->initializer && $this->initializer->__invoke($this->valueHolder, $this, '__get', array('name' => $name), $this->initializer);
 818 | 
 819 |         return $this->valueHolder->$name;
 820 |     }
 821 | 
 822 |     public function __set($name, $value)
 823 |     {
 824 |         $this->initializer && $this->initializer->__invoke($this->valueHolder, $this, '__set', array('name' => $name, 'value' => $value), $this->initializer);
 825 | 
 826 |         $this->valueHolder->$name = $value;
 827 |     }
 828 | 
 829 |     public function __isset($name)
 830 |     {
 831 |         $this->initializer && $this->initializer->__invoke($this->valueHolder, $this, '__isset', array('name' => $name), $this->initializer);
 832 | 
 833 |         return isset($this->valueHolder->$name);
 834 |     }
 835 | 
 836 |     public function __unset($name)
 837 |     {
 838 |         $this->initializer && $this->initializer->__invoke($this->valueHolder, $this, '__unset', array('name' => $name), $this->initializer);
 839 | 
 840 |         unset($this->valueHolder->$name);
 841 |     }
 842 | 
 843 |     public function __clone()
 844 |     {
 845 |         $this->initializer && $this->initializer->__invoke($this->valueHolder, $this, '__clone', array(), $this->initializer);
 846 | 
 847 |         $this->valueHolder = clone $this->valueHolder;
 848 |     }
 849 | 
 850 |     public function __sleep()
 851 |     {
 852 |         $this->initializer && $this->initializer->__invoke($this->valueHolder, $this, '__sleep', array(), $this->initializer);
 853 | 
 854 |         return array('valueHolder');
 855 |     }
 856 | 
 857 |     public function __wakeup()
 858 |     {
 859 |         unset($this->foo);
 860 |     }
 861 | 
 862 |     public function setProxyInitializer(\Closure $initializer = null)
 863 |     {
 864 |         $this->initializer = $initializer;
 865 |     }
 866 | 
 867 |     public function getProxyInitializer()
 868 |     {
 869 |         return $this->initializer;
 870 |     }
 871 | 
 872 |     public function initializeProxy()
 873 |     {
 874 |         return $this->initializer && $this->initializer->__invoke($this->valueHolder, $this, 'initializeProxy', array(), $this->initializer);
 875 |     }
 876 | 
 877 |     public function isProxyInitialized()
 878 |     {
 879 |         return null !== $this->valueHolder;
 880 |     }
 881 | 
 882 |     public function getWrappedValueHolderValue()
 883 |         {
 884 |         return $this->valueHolder;
 885 |     }
 886 | }
887 |
888 | 889 |
890 |

Introducing ProxyManager

891 |

892 | ProxyManager on Github 898 | https://github.com/Ocramius/ProxyManager 899 |

900 |
901 | 902 |
903 |

Avoid bringing out the generated garbage yourself!

904 |

905 | (Currently) deals with 906 | Virtual Proxies, 907 | Smart References, 908 | Null Objects, 909 | Ghost Objects and 910 | Remote Objects 911 |

912 |
913 | 914 |
915 |

Virtual Proxy with ProxyManager

916 |
917 |
918 |
namespace My\Slow;
 919 | 
 920 | class Foo
 921 | {
 922 |     public function __construct()
 923 |     {
 924 |         sleep(10);
 925 |     }
 926 | 
 927 |     public function doFoo()
 928 |     {
 929 |         echo 'foo!';
 930 |     }
 931 | }
932 | 933 |
934 | 935 |
936 |
require_once 'vendor/autoload.php';
 937 | use ProxyManager\Factory\LazyLoadingValueHolderFactory;
 938 | 
 939 | $factory = new LazyLoadingValueHolderFactory();
 940 | 
 941 | $proxy = $factory->createProxy(
 942 |     'My\Slow\Foo',
 943 |     function (& $wrappedObject, $proxy) {
 944 |         $wrappedObject = new My\Slow\Foo();
 945 | 
 946 |         $proxy->setProxyInitializer(null);
 947 |     }
 948 | );
 949 | 
 950 | $proxy->doFoo();
951 |
952 | 953 |
954 |

Smart Reference with ProxyManager

955 |
956 |
957 |
require_once 'vendor/autoload.php';
 958 | use ProxyManager\Factory\AccessInterceptorValueHolderFactory;
 959 | 
 960 | $factory = new AccessInterceptorValueHolderFactory();
 961 | $db = $factory->createProxy(
 962 |     new \My\Db\Connection(),
 963 |     ['query' => function () {
 964 |         echo "Query being executed!\n";
 965 |     }],
 966 |     ['query' => function () {
 967 |         echo "Query completed!\n";
 968 |     }]
 969 | );
 970 | 
 971 | $db->query();
972 |

Yes, I'm no good at naming classes - really.

973 |
974 | 975 |
976 |

977 | Integrates with 978 |
979 | Symfony2 980 |
981 | and 982 |
983 | Zend Framework 2 984 |

985 |
986 | 987 |
988 |

Virtual (Service) Proxies + Zend Framework 2

989 |
return [
 990 |     'service_manager' => [
 991 |         'delegators' => [
 992 |             'MyServiceName' => ['LazyServiceFactory']
 993 |         ],
 994 |     ],
 995 |     'lazy_services' => [
 996 |         'map' => ['MyServiceName' => 'My\Service\ClassName']
 997 |     ]
 998 | ]
999 |
1000 | 1001 |
1002 |

Virtual (Service) Proxies + Symfony 2

1003 |
<services>
1004 |     <service
1005 |         id="MyServiceName"
1006 |         class="My\Service\ClassName"
1007 |         lazy="true"
1008 |     />
1009 | </services>
1010 |
1011 | 1012 |
1013 |

Some useful libraries

1014 |

1015 | 1016 | Doctrine's Proxy Generator 1017 | 1018 |

1019 |

1020 | ProxyManager 1021 |

1022 |

Flow (Typo3)

1023 |

1024 | schmittjoh/cg-library 1025 |

1026 |
1027 |
1028 |

Proxies and AOP

1029 |

1030 | Ocramius on Github 1036 | lisachenko/go-aop-php 1037 |

1038 |

1039 | Uses proxies for AOP purposes 1040 |

1041 |

1042 | Very interesting usage of autoloaders! 1043 |

1044 |
1045 | 1046 |
1047 |

And before we get to the dirty stuff...

1048 |
1049 | 1050 |
1051 |

Questions?

1052 |

1053 | 1054 | Ocramius on Github 1060 | 1061 | Ocramius/proxy-pattern-in-php 1062 |

1063 |

1064 | Ocramius on Twitter 1070 | Ocramius on Github 1076 | Ocramius 1077 |

1078 |
1079 | 1080 |
1081 |

Proxying Fluent Interfaces

1082 |
1083 | 1084 |
1085 |

1086 | Breaks Virtual Proxies, 1087 | Protection Proxies, and 1088 | Smart References 1089 |

1090 |
1091 | 1092 |
1093 |

More important:

1094 |
1095 | 1096 |
1097 |

Possible broken Encapsulation!

1098 |
1099 | 1100 |
1101 |
class BankAccount {
1102 |     protected $amount = 0;
1103 | 
1104 |     public function pay($amount) {
1105 |         $this->amount -= $amount;
1106 | 
1107 |         return $this;
1108 |     }
1109 | }
1110 |
$account = new BankAccount();
1111 | 
1112 | $account->pay(100)->pay(200)->pay(300);
1113 | 
1114 |
1115 | 1116 |
1117 |

Caution!

1118 |

caution!

1119 |

Dirty hacks!

1120 |
1121 | 1122 |
1123 |
class BankAccountProxy extends BankAccount {
1124 |     public function __construct(BankAccount $wrapped) {
1125 |         $this->amount = & $wrapped->amount;
1126 |     }
1127 | 
1128 |     public function pay($amount) {
1129 |         echo 'Paid ' . $amount . "!\n";
1130 | 
1131 |         return parent::pay($amount);
1132 |     }
1133 | }
1134 |
$account = new BankAccountProxy(new BankAccount());
1135 | 
1136 | $account->pay(100)->pay(200)->pay(300);
1137 | 
1138 |
Paid 100
1139 | Paid 200
1140 | Paid 300
1141 |
1142 | 1143 |
1144 |

Public properties proxying

1145 |

(without property accessors!)

1146 |
1147 | 1148 |
1149 |
class Customer {
1150 |     public $name;
1151 |     public $surname;
1152 | }
1153 | 1154 |
1155 | 1156 |
1157 |

Caution!

1158 |

caution!

1159 |

Dirty hacks!

1160 |
1161 | 1162 |
1163 |
class Customer {
1164 |     public $name;
1165 |     public $surname;
1166 | }
1167 | 1168 |
class CustomerProxy extends Customer {
1169 |     public function __construct(Customer $customer) {
1170 |         unset($this->name, $this->surname);
1171 |         $this->customer = $customer;
1172 |     }
1173 | 1174 |
    public function __set($name, $value) {
1175 |         $this->customer->$name = $value;
1176 |     }
1177 | 
1178 |     public function __get($name) {
1179 |         return $this->customer->$name;
1180 |     }
1181 | 
1182 |     // __isset, __unset
1183 | }
1184 |
1185 | 1186 |
1187 |

Don't do this at home!

1188 | Excel Saga-style warning 1189 |
1190 | 1191 |
1192 |

Thank you!

1193 |

1194 | 1195 | Ocramius on Github 1201 | 1202 | Ocramius/proxy-pattern-in-php 1203 |

1204 |
1205 | 1206 |
1207 |
1208 | 1209 | 1210 | 1211 | 1239 | 1240 | 1254 | 1255 | 1256 | 1257 | -------------------------------------------------------------------------------- /index.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The Proxy Pattern in PHP 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 28 | 29 | 30 | 31 | 32 |
33 | 34 |
35 | 36 | <% _.forEach(slides, function(slide) { %> 37 | <% if (!_.isString(slide) && !_.isArray(slide) && _.isObject(slide)) { %> 38 |
data-markdown="slides/<%= slide.filename %>">
39 | <% } %> 40 | <% if (_.isString(slide)) { %> 41 | <% if (slide.indexOf('.html') !== -1) { %> 42 |
43 | <% } else { if (slide.indexOf('.md') !== -1) { %> 44 |
45 | <% }} %> 46 | <% } else { if (_.isArray(slide)) { %> 47 |
48 | <% _.forEach(slide, function(verticalslide) { %> 49 | <% if (!_.isString(verticalslide) && _.isObject(verticalslide)) { %> 50 |
data-markdown="slides/<%= slide.filename %>">
51 | <% } %> 52 | <% if (verticalslide.indexOf('.html') !== -1) { %> 53 |
54 | <% } else { if (verticalslide.indexOf('.md') !== -1) { %> 55 |
56 | <% }} %> 57 | <% }); %> 58 |
59 | <% }} %> 60 | <% }); %> 61 | 62 |
63 | 64 |
65 | 66 | 67 | 68 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /js/loadhtmlslides.js: -------------------------------------------------------------------------------- 1 | // Modified from markdown.js from Hakim to handle external html files 2 | (function () { 3 | /*jslint loopfunc: true, browser: true*/ 4 | /*globals alert*/ 5 | 'use strict'; 6 | 7 | var querySlidingHtml = function () { 8 | var sections = document.querySelectorAll('[data-html]'), 9 | section; 10 | 11 | for (var j = 0, jlen = sections.length; j < jlen; j++) { 12 | section = sections[j]; 13 | 14 | if (section.getAttribute('data-html').length) { 15 | 16 | var xhr = new XMLHttpRequest(), 17 | url = section.getAttribute('data-html'); 18 | 19 | xhr.onreadystatechange = function () { 20 | if (xhr.readyState === 4) { 21 | if (xhr.status >= 200 && xhr.status < 300) { 22 | section.outerHTML = xhr.responseText; 23 | } else { 24 | section.outerHTML = '
ERROR: The attempt to fetch ' + url + ' failed with the HTTP status ' + xhr.status + '. Check your browser\'s JavaScript console for more details.

'; 25 | } 26 | } 27 | }; 28 | 29 | xhr.open('GET', url, false); 30 | try { 31 | xhr.send(); 32 | } catch (e) { 33 | alert('Failed to get file' + url + '.' + e); 34 | } 35 | } 36 | } 37 | }; 38 | 39 | querySlidingHtml(); 40 | })(); 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "the-proxy-pattern-in-php", 3 | "version": "1.0.0", 4 | "private": true, 5 | "devDependencies": { 6 | "grunt": "~0.4.1", 7 | "grunt-contrib-connect": "~0.5.0", 8 | "grunt-contrib-watch": "~0.5.3", 9 | "grunt-contrib-jshint": "~0.6.4", 10 | "load-grunt-tasks": "~0.1.0", 11 | "grunt-coffeelint": "0.0.7" 12 | }, 13 | "engines": { 14 | "node": ">=0.8.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /slides/index.md: -------------------------------------------------------------------------------- 1 | 2 | # The Proxy Pattern in PHP 3 | 4 | From the terminal, pop in: 5 | 6 | ```yo reveal:slide "Slide Title"``` 7 | -------------------------------------------------------------------------------- /slides/list.json: -------------------------------------------------------------------------------- 1 | ["index.md"] 2 | --------------------------------------------------------------------------------