iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 6
0
自我挑戰組

單元測試開發購物車功能系列 第 6

Day06: 持續優化商品物件

  • 分享至 

  • twitterImage
  •  

Item implements ArrayAccess 這個 interface 後
Item 物件就像是在操作 Array
但事實上只完成了一半的工作,
所以今天就再來重構一下 Item 的物件
讓它可以使用的更靈活一些

接下來的目標是不變更舊有單元測試進行修改
新功能則增加新的單元測試

// tests/ItemTest.php

namespace Recca0120\Cart\Tests;

use Recca0120\Cart\Item;
use PHPUnit\Framework\TestCase;

class ItemTest extends TestCase
{
    /** @test */
    public function 測試商品屬性()
    {
        $attributes = [
            'name' => '商品01',
            'price' => 100,
            'quantity' => 2,
        ];

        $item = new Item($attributes);

        $this->assertEquals($attributes, $item->toArray());
    }

    /** @test */
    public function 使用mehtod來設定屬性()
    {
        $attributes = [
            'name' => '商品01',
            'price' => 100,
            'quantity' => 2,
        ];

        $item = new Item();

        $item->setName($attributes['name']);
        $this->assertSame($attributes['name'], $item->getName());

        $item->setPrice($attributes['price']);
        $this->assertSame($attributes['price'], $item->getPrice());

        $item->setQuantity($attributes['quantity']);
        $this->assertSame($attributes['quantity'], $item->getQuantity());

        $this->assertEquals($attributes, $item->toArray());
    }

    /** @test */
    public function 測試ArrayAccess()
    {
        $attributes = [
            'name' => '商品01',
            'price' => 100,
            'quantity' => 2,
        ];

        $item = new Item();

        $item['name'] = $attributes['name'];
        $this->assertSame($attributes['name'], $item['name']);

        $item['price'] = $attributes['price'];
        $this->assertSame($attributes['price'], $item['price']);

        $item['quantity'] = $attributes['quantity'];
        $this->assertSame($attributes['quantity'], $attributes['quantity']);

        $this->assertEquals($attributes, $item->toArray());
    }
}

原始 Item.php

// src/Item.php

<?php

namespace Recca0120\Cart;

use ArrayAccess;

class Item implements ArrayAccess
{
    private $attributes = [];

    public function __construct($attributes = [])
    {
        $this->attributes = $attributes;
    }

    public function setName($name)
    {
        $this->attributes['name'] = $name;

        return $this;
    }

    public function getName()
    {
        return $this->attributes['name'];
    }

    public function setPrice($price)
    {
        $this->attributes['price'] = $price;

        return $this;
    }

    public function getPrice()
    {
        return $this->attributes['price'];
    }

    public function setQuantity($quantity)
    {
        $this->attributes['quantity'] = $quantity;

        return $this;
    }

    public function getQuantity()
    {
        return $this->attributes['quantity'];
    }

    public function toArray()
    {
        return $this->attributes;
    }

    public function offsetExists($offset)
    {
        $offset = $offset === 'qty' ? 'quantity' : $offset;

        return isset($this->attributes[$offset]);
    }

    public function offsetGet($offset)
    {
        $offset = $offset === 'qty' ? 'quantity' : $offset;

        return $this->attributes[$offset];
    }

    public function offsetSet($offset, $value)
    {
        $offset = $offset === 'qty' ? 'quantity' : $offset;

        $this->attributes[$offset] = $value;
    }

    public function offsetUnset($offset)
    {
        $offset = $offset === 'qty' ? 'quantity' : $offset;

        unset($this->attributes[$offset]);
    }
}

利用 __call 的 magic method 來將所有的 setXXX, getXXX 取代掉


namespace Recca0120\Cart;

use ArrayAccess;
use RuntimeException;

class Item implements ArrayAccess
{
    private $attributes = [];

    public function __construct($attributes = [])
    {
        $this->attributes = $attributes;
    }

    public function __call($method, $paramters)
    {
        if (strpos($method, 'set') === 0) {
            $this->attributes[lcfirst(substr($method, 3))] = $paramters[0];

            return $this;
        }

        if (strpos($method, 'get') === 0) {
            return $this->attributes[lcfirst(substr($method, 3))];
        }

        throw new RuntimeException('Call to undefined method '.__CLASS__.'::'.$method);
    }

    public function toArray()
    {
        return $this->attributes;
    }

    public function offsetExists($offset)
    {
        $offset = $offset === 'qty' ? 'quantity' : $offset;

        return isset($this->attributes[$offset]);
    }

    public function offsetGet($offset)
    {
        $offset = $offset === 'qty' ? 'quantity' : $offset;

        return $this->attributes[$offset];
    }

    public function offsetSet($offset, $value)
    {
        $offset = $offset === 'qty' ? 'quantity' : $offset;

        $this->attributes[$offset] = $value;
    }

    public function offsetUnset($offset)
    {
        $offset = $offset === 'qty' ? 'quantity' : $offset;

        unset($this->attributes[$offset]);
    }
}

修改到這邊記得執行一次 phpunit 來確保程式無誤
接下來就再利用 __get, __set. __isset 這兩個 magic method
讓 Item 能直接存取 $attributes 的屬性內容

// tests/ItemTest.php

<?php

namespace Recca0120\Cart\Tests;

use Recca0120\Cart\Item;
use PHPUnit\Framework\TestCase;

class ItemTest extends TestCase
{
    /** @test */
    public function 測試商品屬性()
    {
        $attributes = [
            'name' => '商品01',
            'price' => 100,
            'quantity' => 2,
        ];

        $item = new Item($attributes);

        $this->assertEquals($attributes, $item->toArray());
    }

    /** @test */
    public function 使用mehtod來設定屬性()
    {
        $attributes = [
            'name' => '商品01',
            'price' => 100,
            'quantity' => 2,
        ];

        $item = new Item();

        $item->setName($attributes['name']);
        $this->assertSame($attributes['name'], $item->getName());

        $item->setPrice($attributes['price']);
        $this->assertSame($attributes['price'], $item->getPrice());

        $item->setQuantity($attributes['quantity']);
        $this->assertSame($attributes['quantity'], $item->getQuantity());

        $this->assertEquals($attributes, $item->toArray());
    }

    /** @test */
    public function 測試ArrayAccess()
    {
        $attributes = [
            'name' => '商品01',
            'price' => 100,
            'quantity' => 2,
        ];

        $item = new Item();

        $item['name'] = $attributes['name'];
        $this->assertSame($attributes['name'], $item['name']);

        $item['price'] = $attributes['price'];
        $this->assertSame($attributes['price'], $item['price']);

        $item['quantity'] = $attributes['quantity'];
        $this->assertSame($attributes['quantity'], $attributes['quantity']);

        $this->assertEquals($attributes, $item->toArray());
    }

    /** @test */
    public function 取得商品屬性()
    {
        $attributes = [
            'name' => '商品01',
            'price' => 100,
            'quantity' => 2,
        ];

        $item = new Item;
        $item['name'] = $attributes['name'];
        $item->price = $attributes['price'];
        $item->setQuantity($attributes['quantity']);

        $this->assertEquals($attributes, $item->toArray());
    }
}
// src/Item.php

namespace Recca0120\Cart;

use ArrayAccess;
use RuntimeException;

class Item implements ArrayAccess
{
    private $attributes = [];

    public function __construct($attributes = [])
    {
        $this->attributes = $attributes;
    }

    public function __get($key)
    {
        return $this->attributes[$key];
    }

    public function __set($key, $value)
    {
        return $this->attributes[$key] = $value;
    }

    public function __isset($key)
    {
        return isset($this->attributes[$key]);
    }

    public function __unset($key)
    {
        unset($this->attributes[$key]);
    }

    public function __call($method, $paramters)
    {
        if (strpos($method, 'set') === 0) {
            $this->attributes[lcfirst(substr($method, 3))] = $paramters[0];

            return $this;
        }

        if (strpos($method, 'get') === 0) {
            return $this->attributes[lcfirst(substr($method, 3))];
        }

        throw new RuntimeException('Call to undefined method '.__CLASS__.'::'.$method);
    }

    public function toArray()
    {
        return $this->attributes;
    }

    public function offsetExists($offset)
    {
        $offset = $offset === 'qty' ? 'quantity' : $offset;

        return isset($this->attributes[$offset]);
    }

    public function offsetGet($offset)
    {
        $offset = $offset === 'qty' ? 'quantity' : $offset;

        return $this->attributes[$offset];
    }

    public function offsetSet($offset, $value)
    {
        $offset = $offset === 'qty' ? 'quantity' : $offset;

        $this->attributes[$offset] = $value;
    }

    public function offsetUnset($offset)
    {
        $offset = $offset === 'qty' ? 'quantity' : $offset;

        unset($this->attributes[$offset]);
    }
}

這樣 Item 這個物件的靈活性就變的非常高了
不過寫到這邊會發現有太多的重覆程式碼
所以可以再一次重構

// src/Item.php

namespace Recca0120\Cart;

use ArrayAccess;
use RuntimeException;

class Item implements ArrayAccess
{
    private $attributes = [];

    public function __construct($attributes = [])
    {
        $this->attributes = $attributes;
    }

    public function __get($key)
    {
        $key = $key === 'qty' ? 'quantity' : $key;

        return $this->attributes[$key];
    }

    public function __set($key, $value)
    {
        $key = $key === 'qty' ? 'quantity' : $key;

        $this->attributes[$key] = $value;

        return $this;
    }

    public function __isset($key)
    {
        $key = $key === 'qty' ? 'quantity' : $key;

        return isset($this->attributes[$key]);
    }

    public function __unset($key)
    {
        $key = $key === 'qty' ? 'quantity' : $key;

        unset($this->attributes[$key]);
    }

    public function __call($method, $paramters)
    {
        if (strpos($method, 'set') === 0) {
            return $this->__set(lcfirst(substr($method, 3)), $paramters[0]);
        }

        if (strpos($method, 'get') === 0) {
            return $this->__get(lcfirst(substr($method, 3)));
        }

        throw new RuntimeException('Call to undefined method '.__CLASS__.'::'.$method);
    }

    public function offsetGet($key)
    {
        return $this->__get($key);
    }

    public function offsetSet($key, $value)
    {
        return $this->__set($key, $value);
    }

    public function offsetExists($key)
    {
        return $this->__isset($key);
    }

    public function offsetUnset($key)
    {
        $this->__unset($key);
    }

    public function toArray()
    {
        return $this->attributes;
    }
}

今天有點累就先優化到這邊,明天再繼續


上一篇
Day05: 將商品重構到購物車
下一篇
Day07: 持續優化商品物件2
系列文
單元測試開發購物車功能12
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言