リファクタリング:Rubyエディション 第一章をPHPで写経

『リファクタリング : Rubyエディション』の第一章で紹介されているコードをPHPで写経してみた。

リファクタリング前。

```php:before.php
<?php
class Movie
{
    const REGULAR     = 0;
    const NEW_RELEASE = 1;
    const CHILDRENS   = 2;

    public $title;
    public $price_code;

    public function __construct($title, $price_code)
    {
        $this->title = $title;
        $this->price_code = $price_code;
    }
}

class Rental
{
    public $movie;
    public $days_rented;

    public function __construct($movie, $days_rented)
    {
        $this->movie = $movie;
        $this->days_rented = $days_rented;
    }
}

class Customer
{
    public $name;
    private $rentals;

    public function __construct($name)
    {
        $this->rentals = array();
        $this->name    = $name;
    }

    public function add_rental($arg)
    {
        $this->rentals[] = $arg;
    }

    public function statement()
    {
        $total_amount = 0;
        $frequent_renter_points = 0;

        $result = "Rental Record for $this->name\n";
        foreach ($this->rentals as $element) {
            $this_amount = 0;

            # Calculate price for each row
            switch ($element->movie->price_code) {
                case Movie::REGULAR:
                    $this_amount += 2;
                    if ($element->days_rented > 2) $this_amount += ($element->days_rented - 2) * 1.5;
                    break;

                case Movie::NEW_RELEASE:
                    $this_amount += $element->days_rented * 3;
                    break;

                case Movie::CHILDRENS:
                    $this_amount += 1.5;
                    if ($element->days_rented > 3) $this_amount += ($element->days_rented - 3) * 1.5;
            }

            # add rental points
            $frequent_renter_points += 1;

            # add bonus for 2 day rental of new release
            if ($element->movie->price_code === Movie::NEW_RELEASE && $element->days_rented > 1) {
                $frequent_renter_points += 1;
            }

            # show rental fee for this rental
            $result .= "\t{$element->movie->title}\t{$this_amount}\n";
            $total_amount += $this_amount;
        }

        // add footer
        $result .= "Amount owed is {$total_amount}\n";
        $result .= "You earned {$frequent_renter_points} frequent renter points";
        return $result;
    }

}
```

リファクタリング後。

```php:after.php
<?php
class Movie
{
    public  $title;
    private $price;

    public function __construct($title, $price)
    {
        $this->title = $title;
        $this->price = $price;
    }

    public function charge($days_rented)
    {
        return $this->price->charge($days_rented);
    }

    public function frequent_renter_points($days_rented)
    {
        return $this->price->frequent_renter_points($days_rented);
    }
}

class RegularPrice
{
    use DefaultPrice;

    public function charge($days_rented)
    {
        $result = 2;
        if ($days_rented > 2) $result += ($days_rented - 2) * 1.5;
        return $result;
    }
}

class NewReleasePrice
{
    public function charge($days_rented)
    {
        return $days_rented * 3;
    }

    public function frequent_renter_points($days_rented)
    {
        return $days_rented > 1 ? 2 : 1;
    }
}

class ChildrensPrice
{
    use DefaultPrice;

    public function charge($days_rented)
    {
        $result = 1.5;
        if ($days_rented > 3) $result += ($days_rented - 3) * 1.5;
        return $result;
    }
}

trait DefaultPrice
{
    public function frequent_renter_points($days_rented)
    {
        return 1;
    }
}

class Rental
{
    public $movie;
    public $days_rented;

    public function __construct($movie, $days_rented)
    {
        $this->movie = $movie;
        $this->days_rented = $days_rented;
    }

    public function charge()
    {
        return $this->movie->charge($this->days_rented);
    }

    public function frequent_renter_points()
    {
        return $this->movie->frequent_renter_points($this->days_rented);
    }
}

class Customer
{
    public $name;
    private $rentals;

    public function __construct($name)
    {
        $this->rentals = array();
        $this->name    = $name;
    }

    public function add_rental($arg)
    {
        $this->rentals[] = $arg;
    }

    private function total_charge()
    {
        return array_sum(array_map(function($e) { return $e->charge(); }, $this->rentals));
    }

    private function total_frequent_renter_points()
    {
        return array_sum(array_map(function($e) { return $e->frequent_renter_points(); }, $this->rentals));
    }

    public function statement()
    {
        $result = "Rental Record for $this->name\n";
        foreach ($this->rentals as $element) {
            # show rental fee for this rental
            $result .= "\t{$element->movie->title}\t{$element->charge()}\n";
        }

        // add footer
        $result .= "Amount owed is {$this->total_charge()}\n";
        $result .= "You earned {$this->total_frequent_renter_points()} frequent renter points";
        return $result;
    }
}
```

## 写経できなかったこと
### attr_reader, attr_accessor系の機能
該当する機能がPHPで発見できなかった。わざわざsetter, getter書くのも面倒なので、リファクタリングの対象になったりしていなければ、とりあえずpublicにしている。

### Rubyのinjectに相当する機能
Ruby版ではこうなっている部分。

```ruby
@rentals.inject(0) { |sum, rental| sum + rental.charge }
```

結果は同じになるので以下のようにしてるが。

```
array_sum(array_map(function($e) { return $e->charge(); }, $this->rentals));
```

このケースではたまたま単純に合計してるだけなのでこれでいいが、もうちょっと複雑になるとarray_walkとか使う必要がある？ のかな。

### html_statementを追加してない
リファクタリング前のソースにはない機能なので飛ばした。

## 所感

* これくらい単純ならだいたい一行 : 一行対応する感じでかけるが細かい部分が面倒
    * $this->が@に比べて重い
    * returnを明示的に書かないといけない
    * switch caseで場合別に代入するところの書き方が冗長な気がするのでなんとかならないかな

* RubyのModuleはtraitでだいたい同じことができる？ のかな