diff --git a/README.md b/README.md index fb64ebd..2217e5c 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Look at one of the following topics to learn more about LaravelShoppingcart * [Usage](#usage) * [Collections](#collections) * [Instances](#instances) +* [Associate a model](#models) * [Exceptions](#exceptions) * [Events](#events) * [Example](#example) @@ -220,6 +221,35 @@ N.B. Keep in mind that the cart stays in the last set instance for as long as yo N.B.2 The default cart instance is called `main`, so when you're not using instances,`Cart::content();` is the same as `Cart::instance('main')->content()`. +## Associate a model +A new feature is associating a model with the items in the cart. Let's say you have a `Product` model in your application. With the new `associate()` method, you can tell the cart that an item in the cart, is associated to the `Product` model. + +That way you can access your model right from the `CartRowCollection`! + +Here is an example: + +```php +add('293ad', 'Product 1', 1, 9.99, array('size' => 'large')); + + +$content = Cart::content(); + + +foreach($content as $row) +{ + echo 'You have ' . $row->qty . ' items of ' . $row->product->name . ' with description: "' . $row->product->description . '" in your cart.'; +} +``` + +The key to access the model is the same as the model name you associated (lowercase). +The `associate()` method has a second optional parameter for specifying the model namespace. + ## Exceptions The Cart package will throw exceptions if something goes wrong. This way it's easier to debug your code using the Cart package or to handle the error based on the type of exceptions. The Cart packages can throw the following exceptions: @@ -230,6 +260,7 @@ The Cart package will throw exceptions if something goes wrong. This way it's ea | *ShoppingcartInvalidPriceException* | When a not numeric price is passed | | *ShoppingcartInvalidQtyException* | When a not numeric quantity is passed | | *ShoppingcartInvalidRowIDException* | When the rowId that got passed doesn't exists in the current cart | +| *ShoppingcartUnknownModelException* | When an unknown model is associated to a cart row | ## Events diff --git a/src/Gloudemans/Shoppingcart/Cart.php b/src/Gloudemans/Shoppingcart/Cart.php index bcc6409..1e82b9d 100644 --- a/src/Gloudemans/Shoppingcart/Cart.php +++ b/src/Gloudemans/Shoppingcart/Cart.php @@ -25,6 +25,20 @@ class Cart { */ protected $instance; + /** + * The Eloquent model a cart is associated with + * + * @var string + */ + protected $associatedModel; + + /** + * An optional namespace for the associated model + * + * @var string + */ + protected $associatedModelNamespace; + /** * Constructor * @@ -55,6 +69,24 @@ class Cart { return $this; } + /** + * Set the associated model + * + * @param string $modelName The name of the model + * @param string $modelNamespace The namespace of the model + * @return void + */ + public function associate($modelName, $modelNamespace = null) + { + $this->associatedModel = $modelName; + $this->associatedModelNamespace = $modelNamespace; + + if( ! class_exists($modelNamespace . '\\' . $modelName)) throw new Exceptions\ShoppingcartUnknownModelException; + + // Return self so the method is chainable + return $this; + } + /** * Add a row to the cart * @@ -411,7 +443,7 @@ class Cart { 'price' => $price, 'options' => new CartRowOptionsCollection($options), 'subtotal' => $qty * $price - )); + ), $this->associatedModel, $this->associatedModelNamespace); $cart->put($rowId, $newRow); @@ -428,7 +460,6 @@ class Cart { protected function updateQty($rowId, $qty) { if($qty <= 0) - if($qty == 0) { return $this->remove($rowId); } diff --git a/src/Gloudemans/Shoppingcart/CartRowCollection.php b/src/Gloudemans/Shoppingcart/CartRowCollection.php index db3199e..761e7a0 100644 --- a/src/Gloudemans/Shoppingcart/CartRowCollection.php +++ b/src/Gloudemans/Shoppingcart/CartRowCollection.php @@ -4,9 +4,33 @@ use Illuminate\Support\Collection; class CartRowCollection extends Collection { - public function __construct($items) + /** + * The Eloquent model a cart is associated with + * + * @var string + */ + protected $associatedModel; + + /** + * An optional namespace for the associated model + * + * @var string + */ + protected $associatedModelNamespace; + + /** + * Constructor for the CartRowCollection + * + * @param array $items + * @param string $associatedModel + * @param string $associatedModelNamespace + */ + public function __construct($items, $associatedModel, $associatedModelNamespace) { parent::__construct($items); + + $this->associatedModel = $associatedModel; + $this->associatedModelNamespace = $associatedModelNamespace; } public function __get($arg) @@ -16,10 +40,18 @@ class CartRowCollection extends Collection { return $this->get($arg); } - return NULL; + if($arg == strtolower($this->associatedModel)) + { + $modelInstance = $this->associatedModelNamespace ? $this->associatedModelNamespace . '\\' .$this->associatedModel : $this->associatedModel; + $model = new $modelInstance; + + return $model->find($this->id); + } + + return null; } - public function search(Array $search) + public function search(array $search) { foreach($search as $key => $value) { diff --git a/src/Gloudemans/Shoppingcart/Exceptions/ShoppingcartUnknownModelException.php b/src/Gloudemans/Shoppingcart/Exceptions/ShoppingcartUnknownModelException.php new file mode 100644 index 0000000..f6e50ab --- /dev/null +++ b/src/Gloudemans/Shoppingcart/Exceptions/ShoppingcartUnknownModelException.php @@ -0,0 +1,3 @@ +assertInstanceOf('Gloudemans\Shoppingcart\CartRowOptionsCollection', $this->cart->content()->first()->options); } -} \ No newline at end of file + public function testCartCanAssociateWithModel() + { + $this->cart->associate('TestProduct'); + + $this->assertEquals('TestProduct', PHPUnit_Framework_Assert::readAttribute($this->cart, 'associatedModel')); + } + + public function testCartCanAssociateWithNamespacedModel() + { + $this->cart->associate('TestProduct', 'Acme\Test\Models'); + + $this->assertEquals('TestProduct', PHPUnit_Framework_Assert::readAttribute($this->cart, 'associatedModel')); + $this->assertEquals('Acme\Test\Models', PHPUnit_Framework_Assert::readAttribute($this->cart, 'associatedModelNamespace')); + } + + public function testCartCanReturnModelProperties() + { + $this->events->shouldReceive('fire')->once()->with('cart.add', m::type('array')); + + $this->cart->associate('TestProduct')->add('293ad', 'Product 1', 1, 9.99); + + $this->assertEquals('This is the description of the test model', $this->cart->get('8cbf215baa3b757e910e5305ab981172')->testproduct->description); + } + + public function testCartCanReturnNamespadedModelProperties() + { + $this->events->shouldReceive('fire')->once()->with('cart.add', m::type('array')); + + $this->cart->associate('TestProduct', 'Acme\Test\Models')->add('293ad', 'Product 1', 1, 9.99); + + $this->assertEquals('This is the description of the namespaced test model', $this->cart->get('8cbf215baa3b757e910e5305ab981172')->testproduct->description); + } + + /** + * @expectedException Gloudemans\Shoppingcart\Exceptions\ShoppingcartUnknownModelException + */ + public function testCartThrowsExceptionOnUnknownModel() + { + $this->cart->associate('NoneExistingModel'); + } + +} + diff --git a/tests/helpers/NamespacedProductModelStub.php b/tests/helpers/NamespacedProductModelStub.php new file mode 100644 index 0000000..887c108 --- /dev/null +++ b/tests/helpers/NamespacedProductModelStub.php @@ -0,0 +1,5 @@ +