Commits

Isidro Merayo Castellano  committed 6fe3a7c

Loading Authors on Request via Virtual Proxies

  • Participants
  • Parent commits 84c3756

Comments (0)

Files changed (12)

File composer.json

     },
     "autoload" : {
         "psr-0" : {
-            "Model" : "src/"
+            "Model": "src/",
+            "Library": "src/"
         }
     }
 }

File data/sql/01-create_database_user.sql

+CREATE DATABASE my_demo_examples;
+GRANT ALL PRIVILEGES ON my_demo_examples.* TO 'demo'@'localhost' IDENTIFIED BY 'demo';
+FLUSH PRIVILEGES;

File data/sql/02-create_tables.sql

+CREATE TABLE posts (
+  id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+  title VARCHAR(100) DEFAULT NULL,
+  content TEXT,
+  PRIMARY KEY (id)
+) ENGINE=INNODB;
+
+CREATE TABLE users (
+  id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+  name VARCHAR(45) DEFAULT NULL,
+  email VARCHAR(45) DEFAULT NULL,
+  PRIMARY KEY (id)
+) ENGINE=INNODB;
+
+CREATE TABLE comments (
+  id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+  content TEXT,
+  user_id INTEGER DEFAULT NULL,
+  post_id INTEGER DEFAULT NULL,
+  PRIMARY KEY (id),
+  INDEX (user_id),
+  INDEX (post_id)
+) ENGINE=INNODB;
+
+-- Error code 1005, SQL state HY000
+-- ALTER TABLE comments ADD  FOREIGN KEY (user_id) REFERENCES users(id);
+-- ALTER TABLE comments ADD  FOREIGN KEY (post_id) REFERENCES posts(id);

File data/sql/03-another_tables_example.sql

+CREATE TABLE product (category INT NOT NULL, id INT NOT NULL,
+                      price DECIMAL,
+                      PRIMARY KEY(category, id)) ENGINE=INNODB;
+CREATE TABLE customer (id INT NOT NULL,
+                       PRIMARY KEY (id)) ENGINE=INNODB;
+CREATE TABLE product_order (no INT NOT NULL AUTO_INCREMENT,
+                            product_category INT NOT NULL,
+                            product_id INT NOT NULL,
+                            customer_id INT NOT NULL,
+                            PRIMARY KEY(no),
+                            INDEX (product_category, product_id),
+                            FOREIGN KEY (product_category, product_id)
+                              REFERENCES product(category, id)
+                              ON UPDATE CASCADE ON DELETE RESTRICT,
+                            INDEX (customer_id),
+                            FOREIGN KEY (customer_id)
+                              REFERENCES customer(id)) ENGINE=INNODB;

File data/sql/05-create_table_author.sql

+CREATE TABLE authors (
+  id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+  name VARCHAR(45) DEFAULT NULL,
+  email VARCHAR(45) DEFAULT NULL,
+  PRIMARY KEY (id)
+) ENGINE=INNODB;
+
+INSERT INTO authors (name,email) 
+SELECT name,email FROM users WHERE id IN (1,5);

File data/sql/demo_blog.mwb

Binary file added.

File src/Library/Database/DatabaseAdapterInterface.php

+<?php
+
+namespace Library\Database;
+
+interface DatabaseAdapterInterface
+{
+    public function connect();
+    public function disconnect();
+    
+    public function prepare($sql, array $options = array());
+    public function execute(array $parameters = array());
+    
+    public function fetch($fetchStyle = null, 
+                            $cursorOrientation = null, 
+                            $cursorOffset = null);
+    public function fetchAll($fetchStyle = null, $column = 0);
+    
+    public function select($table, array $bind, $boolOperator = 'AND');
+    public function insert($table, array $bind);
+    public function update($table, array $bind, $where = '');
+    public function delete($table, $where = '');
+}

File src/Library/Database/PdoAdapter.php

+<?php
+namespace Library\Database;
+
+class PdoAdapter implements DatabaseAdapterInterface
+{
+    protected $config = array();
+    protected $connection;
+    protected $statement;
+    protected $fetchMode = \PDO::FETCH_ASSOC;
+    
+    public function __construct($dsn, 
+                                $username = null, 
+                                $password = null, 
+                                array $driverOptions = array()
+            )
+    {
+        $this->config = compact('dsn', 'username', 'password', 'driverOptions');
+    }
+    
+    public function getStatement()
+    {
+        if ($this->statement === null) {
+        throw new \PDOException('There is no PDOStatement object for use.');
+        }
+        return $this->statement;
+    }
+    
+    public function connect() 
+    {
+        if ($this->connection) {
+            return;
+        }
+        
+        try {
+            $this->connection = new \PDO(
+                    $this->config['dsn'],
+                    $this->config['username'],
+                    $this->config['password'],
+                    $this->config['driverOptions']);
+            $this->connection->setAttribute(\PDO::ATTR_ERRMODE, 
+                                            \PDO::ERRMODE_EXCEPTION);
+            $this->connection->setAttribute(\PDO::ATTR_EMULATE_PREPARES, 
+                                            false);
+        }
+        catch (\PDOException $e) {
+            throw new \RuntimeException($e->getMessage());
+        }
+    }
+    
+    public function disconnect()
+    {
+        $this->connection = null;
+    }
+    
+    public function prepare($sql, array $options = array())
+    {
+        $this->connect();
+        try {
+            $this->statement = $this->connection->prepare($sql, $options);
+            return $this;
+        }
+        catch (\PDOException $e){
+            throw new \RuntimeException($e->getMessage());
+        }
+    }
+    
+    public function execute(array $parameters = array()) {
+        try {
+            $this->getStatement()->execute($parameters);
+            return $this;
+        }
+        catch (\PDOException $e) {
+            throw new \RunTimeException($e->getMessage());
+        }
+    }
+
+    public function countAffectedRows() {
+        try {
+            return $this->getStatement()->rowCount();
+        }
+        catch (\PDOException $e) {
+            throw new \RunTimeException($e->getMessage());
+        }
+    }
+
+    public function getLastInsertId($name = null) {
+        $this->connect();
+        return $this->connection->lastInsertId($name);
+    }
+
+    public function fetch($fetchStyle = null,
+        $cursorOrientation = null, $cursorOffset = null) {
+        if ($fetchStyle === null) {
+            $fetchStyle = $this->fetchMode;
+        }
+
+        try {
+            return $this->getStatement()->fetch($fetchStyle,
+                $cursorOrientation, $cursorOffset);
+        }
+        catch (\PDOException $e) {
+            throw new \RunTimeException($e->getMessage());
+        }
+    }
+
+    public function fetchAll($fetchStyle = null, $column = 0) {
+        if ($fetchStyle === null) {
+            $fetchStyle = $this->fetchMode;
+        }
+
+        try {
+            return $fetchStyle === \PDO::FETCH_COLUMN
+               ? $this->getStatement()->fetchAll($fetchStyle, $column)
+               : $this->getStatement()->fetchAll($fetchStyle);
+        }
+        catch (\PDOException $e) {
+            throw new \RunTimeException($e->getMessage());
+        }
+    }
+
+    public function select($table, array $bind = array(),
+        $boolOperator = "AND") {
+        if ($bind) {
+            $where = array();
+            foreach ($bind as $col => $value) {
+                unset($bind[$col]);
+                $bind[":" . $col] = $value;
+                $where[] = $col . " = :" . $col;
+            }
+        }
+
+        $sql = "SELECT * FROM " . $table
+            . (($bind) ? " WHERE "
+            . implode(" " . $boolOperator . " ", $where) : " ");
+        $this->prepare($sql)
+            ->execute($bind);
+        return $this;
+    }
+
+    public function insert($table, array $bind) {
+        $cols = implode(", ", array_keys($bind));
+        $values = implode(", :", array_keys($bind));
+        foreach ($bind as $col => $value) {
+            unset($bind[$col]);
+            $bind[":" . $col] = $value;
+        }
+
+        $sql = "INSERT INTO " . $table
+            . " (" . $cols . ")  VALUES (:" . $values . ")";
+        return (int) $this->prepare($sql)
+            ->execute($bind)
+            ->getLastInsertId();
+    }
+
+    public function update($table, array $bind, $where = "") {
+        $set = array();
+        foreach ($bind as $col => $value) {
+            unset($bind[$col]);
+            $bind[":" . $col] = $value;
+            $set[] = $col . " = :" . $col;
+        }
+
+        $sql = "UPDATE " . $table . " SET " . implode(", ", $set)
+            . (($where) ? " WHERE " . $where : " ");
+        return $this->prepare($sql)
+            ->execute($bind)
+            ->countAffectedRows();
+    }
+
+    public function delete($table, $where = "") {
+        $sql = "DELETE FROM " . $table . (($where) ? " WHERE " . $where : " ");
+        return $this->prepare($sql)
+            ->execute()
+            ->countAffectedRows();
+    }
+
+}

File src/Model/Mapper/AuthorMapper.php

+<?php
+namespace Model\Mapper;
+use Library\Database\DatabaseAdapterInterface,
+    Model\Author;
+
+class AuthorMapper implements AuthorMapperInterface
+{
+    protected $entityTable = "authors";
+
+    public function __construct(DatabaseAdapterInterface $adapter) {
+        $this->adapter = $adapter;
+    }
+
+    public function fetchById($id) {
+        $this->adapter->select($this->entityTable,
+            array("id" => $id));
+
+        if (!$row = $this->adapter->fetch()) {
+            return null;
+        }
+
+        return new Author($row["name"], $row["email"]);
+    }
+}

File src/Model/Mapper/AuthorMapperInterface.php

+<?php namespace Model\Mapper;
+
+interface AuthorMapperInterface
+{
+    public function fetchById($id);
+}

File src/Model/Proxy/AuthorProxy.php

+<?php
+namespace Model\Proxy;
+
+use Model\Mapper\AuthorMapperInterface,
+    Model\AuthorInterface;
+
+class AuthorProxy implements AuthorInterface
+{
+    protected $author;
+    protected $authorId;
+    protected $authorMapper;
+    
+    public function __construct($authorId, AuthorMapperInterface $authorMapper)
+    {
+        $this->authorId = $authorId;
+        $this->authorMapper = $authorMapper;
+    }
+    
+    public function setId($id) {
+        $this->authorId = $id;
+        return $this;
+    }
+    
+    public function getId() {
+        return $this->authorId;
+    }
+    
+    public function setName($name) {
+        $this->loadAuthor();
+        $this->author->setName($name);
+        return $this;
+    }
+
+    public function getName() {
+        $this->loadAuthor();
+        return $this->author->getName();
+    }
+
+    public function setEmail($email) {
+        $this->loadAuthor();
+        $this->author->setEmail($email);
+        return $this;
+    }
+    
+    public function getEmail() {
+        $this->loadAuthor();
+        return $this->author->getEmail();
+    }
+
+    protected function loadAuthor() {
+        if ($this->author === null) {
+            if(!$this->author = $this->authorMapper
+                                            ->fetchById($this->authorId)) {
+                throw new \UnexpectedValueException("Unable to fetch the author.");
+            }
+        }
+        return $this->author;
+    }
+}

File src/bootstrap.php

 
 require_once __DIR__  . '/../vendor/autoload.php';
 
+use Symfony\Component\ClassLoader\UniversalClassLoader;
+
 $autoloader = new UniversalClassLoader();
 $autoloader->register();