Matthew Weier O'Phinney
ZendCon 2012
Don't Repeat Yourself (DRY),
and avoid
Not Invented Here (NIH) syndrome.
You Ain't Gonna Need It (YAGNI)
Favor Composition over inheritance
Create Contracts for your objects
Objects should do one thing, well.
1 $hash = $_GET['hash'];
2 mysql_connect();
3 $res = mysql_query(
4 "SELECT * FROM pastes WHERE hash = '$hash'"
5 );
6 $row = mysql_fetch_assoc($res);
Why?
1 echo (array_key_exists('content', $this->paste)
2 ? $this->paste['content']
3 : '');
4 echo (array_key_exists('language', $this->paste)
5 ? $this->paste['language']
6 : '');
1 echo $this->paste->content;
2 echo $this->paste->language;
1 class Paste
2 {
3 public $hash;
4 public $language = 'txt'; // programming language of content
5 public $content = '';
6 public $timestamp;
7 public $private = false;
8 }
1 public function testCreateReturnsAHashIdentifier()
2 {
3 $paste = new Paste();
4 $this->service->create($paste);
5 $this->assertRegex(
6 '/^[a-f0-9]{8}$/', $paste->hash
7 );
8 }
1 public function testCanFetchAPasteByHash()
2 {
3 $paste = new Paste();
4 $this->service->create($paste);
5 $test = $this->service->fetch($paste->hash);
6 $this->assertInstanceOf('Paste', $test);
7 $this->assertEquals($paste, $test);
8 }
1 public function testCanFetchCollectionOfPastes()
2 {
3 // seed the service first
4 $count = $this->seedService($this->service);
5 $collection = $this->service->fetchAll();
6 $this->assertInstanceOf(
7 'Zend\Paginator\Paginator');
8 $this->assertEquals($count, count($collection));
9 }
1 public function testPastesAreInReverseChronoOrder()
2 {
3 // seed the service first
4 $this->seedService($this->service);
5 $collection = $this->service->fetchAll();
6 $previous = false;
7 foreach ($collection as $paste) {
8 $this->assertInstanceOf('Paste', $paste);
9 if ($previous) {
10 $this->assertLessThan($previous,
11 $paste->timestamp);
12 }
13 $previous = $paste->timestamp;
14 }
15 }
1 public function testCollectionContainsNoPrivates()
2 {
3 // seed the service first
4 $this->seedService($this->service);
5 $collection = $this->service->fetchAll();
6 foreach ($collection as $paste) {
7 $this->assertInstanceOf('Paste', $paste);
8 $this->assertFalse($paste->private);
9 }
10 }
1 interface PasteServiceInterface {
2 /**
3 * @return Paste
4 */
5 public function create(Paste $paste);
6 /**
7 * @return Paste|null
8 */
9 public function fetch($hash);
10 /**
11 * @return \Zend\Paginator\Paginator
12 */
13 public function fetchAll();
14 /**
15 * @return bool
16 */
17 public function exists($hash);
18 }
1 class MemoryPasteService implements
2 PasteServiceInterface
3 {
4 protected $pastes = array();
5 protected $public;
6 public function create(Paste $paste)
7 {
8 $hash = $this->createHash($paste);
9 $paste->hash = $hash;
10 $this->pastes[$hash] = $paste;
11 if (!$this->pastes->private) {
12 $this->public->insert($paste);
13 }
14 return $paste;
15 }
16 }
1 trait CreateHash {
2 public function createHash(Paste $paste) {
3 $hashSeed = sprintf(
4 '%d:%s:%s',
5 microtime(true),
6 $paste->language,
7 hash('sha1', $paste->content)
8 );
9 do {
10 $hashSeed .= ':' . uniqid();
11 $hash = hash('sha256', $hashSeed);
12 $hash = substr($hash, 0, 8);
13 } while ($this->exists($hash));
14 return $hash;
15 }
16 abstract public function exists($hash);
17 }
1 abstract class CreateHash
2 {
3 public static function createHash(
4 Paste $paste,
5 PasteServiceInterface $service
6 ) {
7 // all is the same except that the "while"
8 // condition becomes:
9 // while ($service->exists($hash))
10 }
11 }
1 class MemoryPasteService implements
2 PasteServiceInterface
3 {
4 use CreateHash;
5
6 /* ... previous code ... */
7
8 public function exists($hash)
9 {
10 return array_key_exists($hash,
11 $this->pastes);
12 }
13 }
1 class MemoryPasteService implements
2 PasteServiceInterface
3 {
4 /* ... previous code ... */
5 public function fetch($hash)
6 {
7 if (!$this->exists($hash)) {
8 return null;
9 }
10 return $this->pastes[$hash];
11 }
12 }
1 class MemoryPasteService implements
2 PasteServiceInterface
3 {
4 /* ... previous code ... */
5
6 public function fetchAll()
7 {
8 $adapter = new IteratorPaginator(
9 $this->public);
10 $paginator = new Paginator($adapter);
11 return $paginator;
12 }
13 }
1 class SortedPastes extends SplMaxHeap {
2 public function insert($value) {
3 if (!$value instanceof Paste) {
4 throw new \InvalidArgumentException();
5 }
6 parent::insert($value);
7 }
8 public function compare($a, $b) {
9 if ($a->timestamp === $b->timestamp) {
10 return 0;
11 }
12 if ($a->timestamp > $b->timestamp) {
13 return 1;
14 }
15 return -1;
16 }
17 }
1 class MemoryPasteService implements
2 PasteServiceInterface
3 {
4 /* ... previous code ... */
5
6 public function __construct()
7 {
8 $this->public = new SortedPastes;
9 }
10 }
1 class MongoPasteService implements
2 PasteServiceInterface {
3 use CreateHash;
4 protected $collection;
5 public function __construct(
6 MongoCollection $collection
7 ) {
8 $this->collection = $collection;
9 }
10 public function create(Paste $paste)
11 {
12 $hash = $this->createHash($paste);
13 $paste->hash = $hash;
14 $data = (array) $paste;
15 $this->collection->insert($data);
16 return $paste;
17 }
18 }
1 class CachingService implements
2 PasteServiceInterface
3 {
4 protected $cache;
5 protected $service;
6 public function __construct(
7 Cache $cache, PasteServiceInterface $service
8 ) {
9 $this->cache = $cache;
10 $this->service = $service;
11 }
12 public function create(Paste $paste)
13 {
14 $paste = $this->service->create($paste);
15 $this->cache->store($paste->hash, $paste);
16 $this->cache->invalidate('list');
17 }
18 }
The goals of software craftsmanship are:
Table of contents | t |
---|---|
Exposé | ESC |
Autoscale | e |
Full screen slides | f |
Presenter view | p |
Source files | s |
Slide numbers | n |
Blank screen | b |
Notes | 2 |
Help | h |