@blog.justoneplanet.info

JavaScript、PHP、MySQLを使ったり

PHPでデザインパターン:FactoryMethod(ファクトリーメソッド)

メリットは「生成と処理を分離し利用側の変更なしに処理(クラス)を追加したりできる」ことである。

■コード

今回使用するのは以下のxmlとcsvファイルの2種類とする。

xml

<data>
    <person>
        <name>John</name>
        <occupation>racer</occupation>
    </person>
    <person>
        <name>Mike</name>
        <occupation>banker</occupation>
    </person>
    <person>
        <name>Nick</name>
        <occupation>runner</occupation>
    </person>
</data>

csv

John,racer
Mike,banker
Nick,runner

template methodで処理の枠だけ定義する。

interface Printer
{
    public function read();
    public function output();
}

以下のCsvFilePrinterクラスとXmlFilePrinterクラスで具体的な処理を記述する。

class CsvFilePrinter implements Printer
{
    private $_filename;
    private $_fh;
    public function __construct($filename)
    {
        $this->_filename = $filename;
    }
    public function read()
    {
        $this->_fh = fopen($this->_filename, 'r');
    }
    public function output()
    {
        print("<table>");
        while($data = fgetcsv($this->_fh)){
            print("<tr>");
            for($i = 0, $n = count($data); $i < $n; $i++){
                print("<td>{$data[$i]}</td>");
            }
            print("</tr>");
        }
        print("</table>");
        fclose($this->_fh);
    }
}
class XmlFilePrinter implements Printer
{
    private $_filename;
    private $_rh;
    public function __construct($filename)
    {
        $this->_filename = $filename;
    }
    public function read()
    {
        $this->_rh = simplexml_load_file($this->_filename);
    }
    public function output()
    {
        print('<table>');
        foreach($this->_rh->person as $person){
            print('<tr>');
            print("<td>{$person->name}</td>");
            print("<td>{$person->occupation}</td>");
            print('</tr>');
        }
        print('</table>');
    }
}

Factory部分

分岐してインスタンスを返すようになっている。

class PrinterFactory
{
    public function getPrinter($filename)
    {
        if(preg_match('/\.csv$/i', $filename)){
            return new CsvFilePrinter($filename);
        }
        elseif(preg_match('/\.xml$/i', $filename)){
            return new XmlFilePrinter($filename);
        }
    }
}

クライアントコード

以下のように中身を意識することなく使用できる。

$printer = new PrinterFactory();
$data = $printer->getPrinter('./dat.xml');
$data->read();
$data->output();
/*
John	racer
Mike	banker
Nick	runner
*/
$data = $printer->getPrinter('./dat.csv');
$data->read();
$data->output();
/*
John	racer
Mike	banker
Nick	runner
*/

PHPでデザインパターン:TemplateMethod(テンプレートメソッド)

メリットは「共通処理をまとめ具体的な処理をサブクラスに実装させられる」こと。

■コード

以下のように共通処理を定義する。具体的な処理が必要な部分は抽象メソッドにしておき、それらをdisplayからコールするようにする。

abstract class Display
{
    private $_data;
    public function __construct($data)
    {
        $this->_data = $data;
    }
    public function getData()
    {
        return $this->_data;
    }
    public function display()
    {
        $this->header();
        $this->body();
        $this->footer();
    }
    abstract protected function header();
    abstract protected function body();
    abstract protected function footer();
}

以下のようにDisplayクラスを継承したクラス(DisplayAsPlain、DisplayAsList)を定義し、それぞれに具体的(⇔抽象)な処理を記述する。

class DisplayAsPlain extends Display
{
    protected function header(){}
    protected function body()
    {
        foreach($this->getData() as $key => $value){
            print("{$key} {$value}\n");
        }
    }
    protected function footer(){}
}

class DisplayAsList extends Display
{
    protected function header()
    {
        print("<ul>\n");
    }
    protected function body()
    {
        foreach($this->getData() as $key => $value){
            print("<li>{$key}: {$value}</li>\n");
        }
    }
    protected function footer()
    {
        print('</ul>');
    }
}

新しい表示形式を加えたい場合は、Displayを継承したクラスを作ってあげればよい。

クライアントコード

$data = array(
    'John' => 'banker',
    'Mike' => 'driver',
    'Nick' => 'scientist'
);
$plain = new DisplayAsPlain($data);
$plain->display();
/*
John banker
Mike driver
Nick scientist
*/
$list = new DisplayAsList($data);
$list->display();
/*
<ul>
<li>John: banker</li>
<li>Mike: driver</li>
<li>Nick: scientist</li>
</ul>
*/

PHPでデザインパターン:Adapter(アダプタ)

メリットは「既存のコードを再利用出来ること」である。

■コード

以下の様なクラスがあるとする。

class SoccerPlayer
{
    private $_name;
    public function __construct($name)
    {
        $this->_name = $name;
    }
    public function introduce()
    {
        print($this->_name);
    }
}

以下の様なインターフェイスがあり、メソッドを「getName」で統一したい。

interface Person
{
    public function getName();
}

更に、使用実績があるSoccerPlayerクラスのコードを変更することなく、「getName」でSoccerPlayerクラスのメソッド「introduce」をcallしたい。そんな要望に応えるのがAdapterパターンだ。

継承の場合

継承の場合はコンストラクタの引数は「$name」であり、内部に保持するのも「$name」である。

class SoccerPerson extends SoccerPlayer implements Person
{
    public function __construct($name)
    {
        parent::__construct($name);
    }
    public function getName()
    {
        parent::introduce();
    }
}
クライアント側

継承を使用しているので、当然ながら元のメソッド「introduce」も使えてしまう。

$john = new SoccerPerson('John');
$john->getName();//John
$john->introduce();//John

委譲の場合

継承の場合はコンストラクタの引数は「$name」であり、内部に保持するのは「SoccerPlayerのインスタンス」である。従ってgetNameした時、そのインスタンス経由でintroduceがコールできる。

class SoccerPerson implements Person
{
    private $_person;
    public function __construct($name)
    {
        $this->_person = new SoccerPlayer($name);
    }
    public function getName()
    {
        $this->_person->introduce();
    }
}
クライアント側

委譲(継承ではない)を使用しているので、メソッド「introduce」は使えない。

$john = new SoccerPerson('John');
$john->getName();//John
$john->introduce();//Fatal error

PHPでデザインパターン:Decorator(装飾)

interface Text{
    public function getText();
    public function setText($str);
}
class PlainText implements Text
{
    private $_textString = null;
    public function getText()
    {
        return $this->_textString;
    }
    public function setText($str)
    {
        $this->_textString = $str;
    }
}
abstract class TextDecorator implements Text
{
    private $_text;
	public function __construct(Text $target)
	{
	    $this->_text = $target;
    }
    public function getText()
    {
        return $this->_text->getText();
    }
    public function setText($str)
    {
        $this->_text->setText($str);
    }
}
class UpperCaseText extends TextDecorator
{
    public function __construct(Text $target)
    {
        parent::__construct($target);
    }
    public function getText()
    {
        $str = parent::getText();
        $str = strtoupper($str);
        return $str;
    }
}
class DoubleByteText extends TextDecorator
{
    public function __construct(Text $target)
    {
        parent::__construct($target);
    }
    public function getText()
    {
        $str = parent::getText();
        $str = mb_convert_kana($str, 'RANSKV');
        return $str;
    }
}
$text = new PlainText();
$text->setText('This is a pen.');
$dbText = new DoubleByteText($text);
var_dump($dbText->getText());//string(42) "This is a pen."
$ucText = new UpperCaseText($text);
var_dump($ucText->getText());//string(14) "THIS IS A PEN."

PHPでデザインパターン:Iterator(イテレータ)

Student.php

class Student
{
    private $name;
    private $age;
    private $sex;
    public function __construct($name, $age, $sex)
    {
        $this->name = $name;
        $this->age  = $age;
        $this->sex  = $sex;
    }
    public function getName()
    {
        return $this->name;
    }
    public function getAge()
    {
        return $this->age;
    }
    public function getSex()
    {
        return $this->sex;
    }
}

Students.php

class Students iplements IteratorAggregate
{
    private $students;
    public function __construct()
    {
        $this->students = new ArrayObject();
    }
    public function add(Student $student)
    {
        $this->students[] = $student;
    }
    public function getIterator()
    {
        return $this->students->getIterator();
    }
}

MaleIterator.php

require_once 'Student.php';

class MaleIterator extends FileterIterator
{
    public function __construct($iterator)
    {
        parent::__construct($iterator);
    }
    public function accept()
    {
        $student = $this->current();
        return ($student->getSex() === 'male');
    }
}

■クライアントコード

require_once 'Student.php';
require_once 'Students.php';
require_once 'MaleIterator.php';

$students = new Students();
$students->add(new Student('John', 12, 'male'));
$students->add(new Student('Jack', 13, 'male'));
$students->add(new Student('Emily', 14, 'female'));

$iterator = $students->getIterator();
while($iterator->valid()){
    $student = $iterator->current();
    $list[] = array(
        'name' => $student->getName(),
        'age'  => $student->getAge(),
        'sex'  => $student->getSex()
    );
    $student = $iterator->next();
}
var_dump($list);
/*
array(3) {
  [0]=>
  array(3) {
    ["name"]=>
    string(4) "John"
    ["age"]=>
    int(12)
    ["sex"]=>
    string(4) "male"
  }
  [1]=>
  array(3) {
    ["name"]=>
    string(4) "Jack"
    ["age"]=>
    int(13)
    ["sex"]=>
    string(4) "male"
  }
  [2]=>
  array(3) {
    ["name"]=>
    string(5) "Emily"
    ["age"]=>
    int(14)
    ["sex"]=>
    string(6) "female"
  }
}
*/
$iterator = new MaleIterator($iterator);
foreach($iterator as $student){
    $list[] = array(
        'name' => $student->getName(),
        'age'  => $student->getAge(),
        'sex'  => $student->getSex()
    );
}
var_dump($list);
/*
array(2) {
  [0]=>
  array(3) {
    ["name"]=>
    string(4) "John"
    ["age"]=>
    int(12)
    ["sex"]=>
    string(4) "male"
  }
  [1]=>
  array(3) {
    ["name"]=>
    string(4) "Jack"
    ["age"]=>
    int(13)
    ["sex"]=>
    string(4) "male"
  }
}
*/

きれいだ。

PHPでデザインパターン:Builder(ビルダー)

Item.class.php

<?php
class Item
{
    private $_title;
    private $_url;
    private $_date;
    public function __construct($title, $url, $date)
    {
        $this->_title = $title;
        $this->_url  = $url;
        $this->_date = $date;
    }
    public function getTitle()
    {
        return $this->_title;
    }
    public function getUrl()
    {
        return $this->_url;
    }
    public function getDate()
    {
        return $this->_date;
    }
}

NewsDirector.class.php

<?php
require_once 'class/NewsDirector.class.php';

class NewsDirector
{
    private $_builder;
    private $_url;
    public function __construct(NewsBuilder $builder, $url)
    {
        $this->_builder = $builder;
        $this->_url     = $url;
    }
    public function getNews()
    {
        $list = $this->_builder->parse($this->_url)
        return $list
    }
}

NewsBuilder.class.php

<?php
interface NewsBuilder
{
    public function parse($data);
}

FeedNewsBuilder.class.php

<?php
require_once 'Item.class.php';
require_once 'NewsBuilder.class.php';

class FeedNewsBuilder implements NewsBuilder
{
    public function parse($url)
    {
        $data = new SimpleXmlElement($url, null, true);
        if($data){
            foreach($data->item as $item){
                $dc = $item->children('http://purl.org/dc/elements/1.1/');
                $list[] = new News($item->title, $item->link, $dc->date);
            }
            return $list;
        }
    }
}

index.php

クライアントとやり取りするフロント部分だ。

<?php
require_once 'class/NewsDirector.class';
require_once 'class/FeedNewsBuilder.class.php';

$builder = new FeedNewsBuilder();
$url = 'http://blog.justoneplanet.info/feed/';
$director = new NewsDirector($builder, $url);
foreach($director->getNews() as $article){
    $data = array(
        'date'  => $article->getDate(),
        'url'   => $article->getUrl(),
        'title' => $article->getTitle()
    );
}

The Standard PHP Library(SPL)

■ArrayAccess

以下のインターフェイスを実装することにより、配列のようにオブジェクトへアクセスができる。但し、ビルトインインターフェイスであり、以下のコードは記述する必要はない。

<?php
interface ArrayAccess {
    function offsetSet($offset, $value);
    function offsetGet($offset);
    function offsetUnset($offset);
    function offsetExists($offset);
}
?>
offfsetSet
配列に値をセットする
offsetGet
配列から値を得る
offsetUnset
配列から値を削除する
offsetExists
配列に要素が存在するか判定する

実例

<?php
class MyArray implements ArrayAccess {
    protected $array();
    public function offsetSet($offset, $value){
        if(!is_int($offset)){
            throw new Exception();
        }
        $this->array[$offset] = $value;
    }
    public function offsetGet($offset){
        return $this->array[$offset];
    }
    public function offsetUnset($offset){
        unset($this->array[$offset]);
    }
    public function offsetExists($offset){
        return array_key_exists($this->array, $offset);
    }
}

$obj = new MyArray();
$obj[1] = 2;
$obj['a'] = 1;//throws exception
?>

■Iterator

<?php
interface Iterator {
    function current();
    function next();
    function rewind();
    function key();
    function valid();
}
?>

実例

<?php
class MyData implements Iterator {
    private $myData = array(
        "Mike",
        "Jack",
        "Emily",
        "Naomi"
    );
    private $current = 0;
    public function current(){
        return $this->myData[$this->current];
    }
    public function next(){
        $this->current++;
    }
    public function rewind(){
        $this->current = 0;
    }
    public function key(){
        return $this->current;
    }
    public function valid(){
        return isset($this->myData[$this->current]);
    }
}

$data = new MyData();
foreach($data as $key => $value){
    print("{$key} : {$value}");
}
?>

■SeekableIterator

<?php
interface SeekableIterator {
    function current();
    function next();
    function rewind();
    function key();
    function valid();
    function seek($index);
}
?>

■Recursive Iteration

<?php
RecursiveIteratorIterator   implements OuterIterator, Traversable, Iterator {
    public function current(void);
    public function getDepth(void);
    public function getSubIterator(void);
    public function key(void);
    public function next(void);
    public function rewind(void);
    public function valid(void);
}
?>

実例

<?php
class Company_Iterator extends RecursiveIteratorIterator {
    public function beginChildren(){
        if($this->getDepth() >= 3){
            print(str_repeat("t", $this->getDepth() - 1));
            print('<ul>');
        }
    }
    public function endChildren(){
        if($this->getDepth() >= 3){
            print(str_repeat("t", $this->getDepth() - 1));
            print('</ul>');
        }
    }
}
class RecursiveArrayObject extends ArrayObject {
    function getIterator(){
        return new RecursiveArrayIterator($this);
    }
}
$company = array(
    array("Acme Anvil Co."),
    array(
        array(
            "Human Resources",
            array(
                'Nick',
                'Jack',
                'Mike'
            )
        ),
        array(
            "Accounting",
            array(
                'John',
                'Emily',
                'Naomi'
            )
        )
    )
);

$it = new Company_Iterator(new RecursiveArrayObject($company));
foreach($it as $item){
    print(str_replace("t", $it->getDepth()));
    switch($it->getDepth()){
        case 1:
            print("<h1>Company: {$item}</h1>" . PHP_EOL);
            break;
        case 2:
            print("<h2>Department: {$item}</h2>" . PHP_EOL);
            break;
        default:
            print("<li>{$item}</li>" . PHP_EOL);
    }
}
/*
	<h1>Company: Acme Anvil Co.</h1>
		<h2>Department: Human Resources</h2>
		<ul>
			<li>Nick</li>
			<li>Jack</li>
			<li>Mike</li>

		</ul>
		<h2>Department: Accounting</h2>
		<ul>
			<li>John</li>
			<li>Emily</li>
			<li>Naomi</li>
		</ul>
*/
?>

■Recursive Iteration

<?php
class NumberFilter extends FilterIterator {
    const FILTER_EVEN = 1;
    const FILTER_ODD = 2;
    private $_type;
    public function __construct($iterator, $odd_or_even = self::FILTER_EVEN){
        $this->_type = $odd_or_even;
        parent::__construct($iterator);
    }
    public function accept(){
        if($this->_type === self::FILTER_EVEN){
            return ($this->current() % 2 == 0);
        }
        else{
            return ($this->current() % 2 == 1);
        }
    }
}

$numbers = new ArrayObject(range(0, 10));
$number_it = new ArrayIterator($numbers);

$it = new NumberFilter($numbers_it, NumberFilter::FILTER_ODD)

foreach($it as $number){
    print("{$number}" . PHP_EOL);
}
?>

PHP Design Pattern(PHPによるデザインパターン)

■シングルトンパターン(Singleton Pattern)

シングルトンパターンとは、ある特定のクラスのインスタンスが特定の数しか生成されない事を保証するパターンである。

<?php
class Db {
    protected static $dbh;
    protected static $instance;
    protected __construct(){
        self::$dbh = new PDO($dsn, DB_USER, DB_PASS);
    }
    public static function getDbh(){
        if(!$dbh){
            self::$instance = new self();
        }
        return self::$dbh;
    }
}
$dbh = Db::getDbh();
?>

但し、上述の例はいささか不完全でもある。何故ならば$dbhがDbクラスのインスタンスでない限り、クローン化はどうしても防げないからだ。

<?php
final class ForbiddenClonePDO extends PDO {
    final public function __clone(){
        throw new RuntimeException();
    }
}
class Db {
    protected static $dbh;
    protected static $instance;
    protected __construct(){
        try{
            $dsn = 'mysql:host=localhost;dbname=dbname';
            self::$dbh = new ForbiddenClonePDO($dsn, DB_USER, DB_PASS);
            self::$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
            self::$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        }
        cacth(PDOException $e){
            //code
        }
    }
    public static function getDbh(){
        if(!$dbh){
            self::$instance = new self();
        }
        return self::$dbh;
    }
}
$dbh = Db::getDbh();
?>

■ファクトリーパターン(Factory Pattern)

似たようなタスクを違った手法で行う

<?php
class Configuration {
    const STORE_INI = 1;
    const STORE_DB = 2;
    const STORE_XML = 3;
    public static function getStore($type = self::STORE_XML){
        switch($type){
            case self::STORE_INI:
                return new Configuration_Ini();
            case self::STORE_DB:
                return new Configuration_Db();
            case self::STORE_XML:
                return new Configuration_Xml();
            default:
                throw new Exception();
        }
    }
}
class Configuration_Ini {
    //code
}
class Configuration_Db {
    //code
}
class Configuration_Xml {
    //code
}
$config = Configuration::getStore(Configuration::STORE_XML);
?>

■レジストリパターン(Registry Pattern)

<?php
class Registry {
    private static $_register;
    public static function add(&$item, $name = null){
        if(is_object($item) && is_null($name)){
            $name = getClass($item);
        }
        elseif(is_null($name)){
            throw new Exception();
        }
        $name = strtolower($name);
        self::$_register[$name] = $item;
    }
    public static function &get($name){
        $name = strtolower($name);
        if(array_key_exists($name, self::$_register)){
            return self::$_register[$name];
        }
        else{
            throw new Exception();
        }
    }
    public static function exists($name){
        $name = strtolower($name);
        if(array_key_exists($name, self::$register)){
            return true;
        }
        else{
            return false;
        }
    }
}

$db = new DB();
Registry::add($db);

if(Registry::exists('DB')){
    $db = Registry::get('DB');
}
else{
    die();
}
?>