From 2342fc18780eb2b8c165c67d63898f43abb83e29 Mon Sep 17 00:00:00 2001 From: Nicolas Tiran Date: Fri, 7 Dec 2018 14:50:57 +0100 Subject: [PATCH] Initial commit --- .gitignore | 6 + LICENSE | 19 + README.md | 157 ++++ bin/install.sh | 12 + bin/pre-commit.sh | 33 + composer.json | 35 + phpunit.xml.dist | 8 + src/AntiMattr/Common/Product/Attribute.php | 157 ++++ .../Common/Product/AttributeInterface.php | 87 +++ src/AntiMattr/Common/Product/Image.php | 127 ++++ .../Common/Product/ImageInterface.php | 62 ++ src/AntiMattr/Common/Product/Meta.php | 47 ++ src/AntiMattr/Common/Product/Option.php | 132 ++++ .../Common/Product/OptionInterface.php | 74 ++ src/AntiMattr/Common/Product/Product.php | 682 ++++++++++++++++++ .../Common/Product/ProductInterface.php | 312 ++++++++ src/AntiMattr/Common/Product/Variation.php | 169 +++++ .../Common/Product/VariationInterface.php | 88 +++ .../Tests/Common/Product/AttibuteTest.php | 88 +++ .../Tests/Common/Product/ImageTest.php | 59 ++ .../Tests/Common/Product/MetaTest.php | 21 + .../Tests/Common/Product/OptionTest.php | 71 ++ .../Tests/Common/Product/ProductTest.php | 394 ++++++++++ .../Tests/Common/Product/VariationTest.php | 126 ++++ 24 files changed, 2966 insertions(+) create mode 100755 .gitignore create mode 100755 LICENSE create mode 100755 README.md create mode 100755 bin/install.sh create mode 100755 bin/pre-commit.sh create mode 100755 composer.json create mode 100755 phpunit.xml.dist create mode 100755 src/AntiMattr/Common/Product/Attribute.php create mode 100755 src/AntiMattr/Common/Product/AttributeInterface.php create mode 100755 src/AntiMattr/Common/Product/Image.php create mode 100755 src/AntiMattr/Common/Product/ImageInterface.php create mode 100755 src/AntiMattr/Common/Product/Meta.php create mode 100755 src/AntiMattr/Common/Product/Option.php create mode 100755 src/AntiMattr/Common/Product/OptionInterface.php create mode 100755 src/AntiMattr/Common/Product/Product.php create mode 100755 src/AntiMattr/Common/Product/ProductInterface.php create mode 100755 src/AntiMattr/Common/Product/Variation.php create mode 100755 src/AntiMattr/Common/Product/VariationInterface.php create mode 100755 tests/AntiMattr/Tests/Common/Product/AttibuteTest.php create mode 100755 tests/AntiMattr/Tests/Common/Product/ImageTest.php create mode 100755 tests/AntiMattr/Tests/Common/Product/MetaTest.php create mode 100755 tests/AntiMattr/Tests/Common/Product/OptionTest.php create mode 100755 tests/AntiMattr/Tests/Common/Product/ProductTest.php create mode 100755 tests/AntiMattr/Tests/Common/Product/VariationTest.php diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..8dfe4d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +composer.phar +vendor/ + +# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file +# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file +composer.lock diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..b9e9467 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 AntiMattr Common Product Model + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100755 index 0000000..462fd3c --- /dev/null +++ b/README.md @@ -0,0 +1,157 @@ +common-product +============== + +The AntiMattr common product is a library that provides a shared Product interface. + +Installation +============ + +Add the following to your composer.json file: + +```json +{ + "require": { + "antimattr/common-product": "~1.0@stable" + } +} +``` + +Install the libraries by running: + +```bash +composer install +``` + +If everything worked, the Common Product can now be found at vendor/antimattr/common-product. + +Model +===== + +```javascript +/* + * A configurable Product has Attributes (ex. size, color) + * Those Attributes have Options (ex. red, blue, small, medium) + * A configurable Product has Variations which are unique combinations of Options. + */ + +Product = { + attributes: [ + Attribute, // color + Attribute, // size + ], + createdAt: Date, + currency: string, // USD + description: string, + dimensionUnit: string, // For Height, Length, Width + height: int, // Smallest unit for measurement system + id: string || int, + images: [ + Image, + Image, + Image, + ], + length: int, // Smallest unit for measurement system + mpn: string, + msrp: int, // Smallest unit for currency + price: int, // Smallest unit for currency + publishedAt: Date, + quantity: int, + sku: string, + status: string, + title: string, + upc: string, + updatedAt: Date, + variations: [ + Variation, // color_red size_small (an object containing to Variants, one for color red and one for size medium) + Variation, // color_red size_medium + Variation, // color_red size_large + Variation, // color_blue size_small + Variation, // color_blue size_medium + Variation // color_blue size_large + ], + weight: int, // Smallest unit for measurement system + weigthUnit: string, + width: int, // Smallest unit for measurement system +}; + +Attribute { + id: 'id', + name: 'Color', + options: [ + Option, // red + Option, // orange + Option, // yellow + Option, // green + Option, // blue + Option, // indigo + Option, // violet + ] +}; + +Option { + attribute: Attribute, // ReferenceOne + value: 'Red', + uniqueIdentifier: 'color_red', // Concatenate Attribute name and variant value. This ensures there can be only one "red". + variation: Variation, +}; + +Variation extends Product { + image: Image, + options: [ // A single permutation of options creating a unique mapping of options + Option, // color_red + Option, // size_medium + ], + position: int, // Default sort order for Variations, + product: Product, // Parent Product + uniqueIdentifier: 'color_red_size_medium', // Concatenate Option::uniqueIdentifier +}; +``` + +Pull Requests +============= + +Pull Requests - PSR Standards +----------------------------- + +Please use the pre-commit hook to run the fix all code to PSR standards + +Install once with + +```bash +./bin/install.sh +Copying /antimattr-common-product/bin/pre-commit.sh -> /antimattr-common-product/bin/../.git/hooks/pre-commit +``` + +Pull Requests +============= + +Pull Requests - PSR Standards +----------------------------- + +Please use the pre-commit hook to fix all code to PSR standards + +Install once with + +```bash +./bin/install.sh +Copying /antimattr-common-product/bin/pre-commit.sh -> /antimattr-common-product/bin/../.git/hooks/pre-commit +``` + +Pull Requests - Testing +----------------------- + +Please make sure tests pass + +```bash +$ vendor/bin/phpunit tests +``` + +Pull Requests - Code Sniffer and Fixer +-------------------------------------- + +Don't have the pre-commit hook running, please make sure to run the fixer/sniffer manually + +```bash +$ vendor/bin/php-cs-fixer fix src/ +$ vendor/bin/php-cs-fixer fix tests/ +``` diff --git a/bin/install.sh b/bin/install.sh new file mode 100755 index 0000000..c9c3332 --- /dev/null +++ b/bin/install.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +CURRENT_DIRECTORY=`pwd` +BIN_DIRECTORY="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +PROJECT_DIRECTORY="$BIN_DIRECTORY/.." + +echo "Copying $BIN_DIRECTORY/pre-commit.sh -> $PROJECT_DIRECTORY/.git/hooks/pre-commit" + +cp "$BIN_DIRECTORY/pre-commit.sh" "$PROJECT_DIRECTORY/.git/hooks/pre-commit" +chmod 0777 "$PROJECT_DIRECTORY/.git/hooks/pre-commit" +cd $CURRENT_DIRECTORY; diff --git a/bin/pre-commit.sh b/bin/pre-commit.sh new file mode 100755 index 0000000..27f10b9 --- /dev/null +++ b/bin/pre-commit.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +echo "pre commit hook start" + +CURRENT_DIRECTORY=`pwd` +GIT_HOOKS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +PROJECT_DIRECTORY="$GIT_HOOKS_DIR/../.." + +cd $PROJECT_DIRECTORY; +PHP_CS_FIXER="vendor/bin/php-cs-fixer" + +HAS_PHP_CS_FIXER=false + +if [ -x "$PHP_CS_FIXER" ]; then + HAS_PHP_CS_FIXER=true +fi + +if $HAS_PHP_CS_FIXER; then + git status --porcelain | grep -e '^[AM]\(.*\).php$' | cut -c 3- | while read line; do + ${PHP_CS_FIXER} fix --verbose ${line}; + git add "$line"; + done +else + echo "" + echo "Please install php-cs-fixer, e.g.:" + echo "" + echo " composer require --dev fabpot/php-cs-fixer:dev-master" + echo "" +fi + +cd $CURRENT_DIRECTORY; +echo "pre commit hook finish" diff --git a/composer.json b/composer.json new file mode 100755 index 0000000..2b9cd61 --- /dev/null +++ b/composer.json @@ -0,0 +1,35 @@ +{ + "name": "antimattr/common-product", + "type": "library", + "description": "Common Product Model", + "keywords": ["antimattr", "product"], + "homepage": "http://github.com/antimattr/common-product", + "license": "MIT", + "authors": [ + {"name": "Matthew Fitzgerald", "email": "matthewfitz@gmail.com"} + ], + "require": { + "php": ">=5.3.2", + "doctrine/collections": "1.*" + }, + "require-dev": { + "antimattr/test-case": "~1.0@stable", + "phpunit/phpunit": "~4.0", + "fabpot/php-cs-fixer": "dev-master" + }, + "minimum-stability": "dev", + "autoload": { + "psr-0": { + "AntiMattr\\Common\\Product\\": "src/" + } + }, + "autoload-dev": { + "psr-0": { + "AntiMattr\\TestCase\\": "vendor/antimattr/test-case/src", + "AntiMattr\\Tests\\Common\\Product\\": "tests/" + } + }, + "archive": { + "exclude": ["bin", "tests", "*phpunit.xml"] + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100755 index 0000000..d225cd3 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,8 @@ + + + + + ./tests/ + + + \ No newline at end of file diff --git a/src/AntiMattr/Common/Product/Attribute.php b/src/AntiMattr/Common/Product/Attribute.php new file mode 100755 index 0000000..db6ddf8 --- /dev/null +++ b/src/AntiMattr/Common/Product/Attribute.php @@ -0,0 +1,157 @@ + + */ +class Attribute implements AttributeInterface +{ + /** @var string */ + protected $id; + + /** @var ArrayAccess */ + protected $meta; + + /** @var string */ + protected $name; + + /** @var Doctrine\Common\Collections\Collection */ + protected $options; + + public function __construct() + { + $this->meta = new Meta(); + $this->options = new ArrayCollection(); + } + + /** + * Alphanumeric underscore + * + * @return string + */ + public function getCanonicalName() + { + $value = (string) $this->name; + $value = strtolower($value); + // Keep Alphanumeric, dashes and underscores + $value = preg_replace("/[^a-z0-9_\s-]/", "", $value); + // Remove repeating whitespace or underscores + $value = preg_replace("/[\s_]+/", " ", $value); + // Convert whitespaces and dash to underscore + return preg_replace("/[\s-]/", "_", $value); + } + + /** + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * @param string + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return ArrayAccess + */ + public function getMeta() + { + return $this->meta; + } + + /** + * @param ArrayAccess + */ + public function setMeta(ArrayAccess $meta) + { + $this->meta = $meta; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @param AntiMattr\Common\Product\OptionInterface + */ + public function addOption(OptionInterface $option) + { + $this->options->add($option); + if ($this !== $option->getAttribute()) { + $option->setAttribute($this); + } + } + + /** + * @return Doctrine\Common\Collections\Collection + */ + public function getOptions() + { + return $this->options; + } + + /** + * @param AntiMattr\Common\Product\OptionInterface + * + * @return bool + */ + public function hasOption(OptionInterface $option) + { + return $this->options->contains($option); + } + + /** + * @param AntiMattr\Common\Product\OptionInterface + * + * @throws OutOfBoundsException + */ + public function removeOption(OptionInterface $option) + { + $success = $this->options->removeElement($option); + + if (!$success) { + throw new OutOfBoundsException('Attribute::options do not contain option to remove'); + } + } + + /** + * @param Doctrine\Common\Collections\Collection + */ + public function setOptions(Collection $options) + { + $this->options = $options; + } +} diff --git a/src/AntiMattr/Common/Product/AttributeInterface.php b/src/AntiMattr/Common/Product/AttributeInterface.php new file mode 100755 index 0000000..62f568a --- /dev/null +++ b/src/AntiMattr/Common/Product/AttributeInterface.php @@ -0,0 +1,87 @@ + + */ +interface AttributeInterface +{ + /** + * Alphanumeric underscore + * + * @return string + */ + public function getCanonicalName(); + + /** + * @return string + */ + public function getId(); + + /** + * @param string + */ + public function setId($id); + + /** + * @return ArrayAccess + */ + public function getMeta(); + + /** + * @param ArrayAccess + */ + public function setMeta(ArrayAccess $meta); + + /** + * @return string + */ + public function getName(); + + /** + * @param string + */ + public function setName($name); + + /** + * @param AntiMattr\Common\Product\OptionInterface + */ + public function addOption(OptionInterface $option); + + /** + * @return Doctrine\Common\Collections\Collection + */ + public function getOptions(); + + /** + * @param AntiMattr\Common\Product\OptionInterface + * + * @return bool + */ + public function hasOption(OptionInterface $option); + + /** + * @param AntiMattr\Common\Product\OptionInterface + * + * @throws OutOfBoundsException + */ + public function removeOption(OptionInterface $option); + + /** + * @param Doctrine\Common\Collections\Collection + */ + public function setOptions(Collection $options); +} diff --git a/src/AntiMattr/Common/Product/Image.php b/src/AntiMattr/Common/Product/Image.php new file mode 100755 index 0000000..1e01272 --- /dev/null +++ b/src/AntiMattr/Common/Product/Image.php @@ -0,0 +1,127 @@ + + */ +class Image implements ImageInterface +{ + /** @var string */ + protected $id; + + /** @var ArrayAccess */ + protected $meta; + + /** @var int */ + protected $position; + + /** @var AntiMattr\Common\Product\ProductInterface */ + protected $product; + + /** @var string */ + protected $source; + + public function __construct() + { + $this->meta = new Meta(); + } + + /** + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * @param string + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return ArrayAccess + */ + public function getMeta() + { + return $this->meta; + } + + /** + * @param ArrayAccess + */ + public function setMeta(ArrayAccess $meta) + { + $this->meta = $meta; + } + + /** + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * @param int + * + * @throws InvalidArgumentException + */ + public function setPosition($position) + { + if (!is_int($position)) { + throw new InvalidArgumentException('Image::position must be an integer'); + } + + $this->position = $position; + } + + /** + * @return AntiMattr\Common\Product\ProductInterface + */ + public function getProduct() + { + return $this->product; + } + + /** + * @param AntiMattr\Common\Product\ProductInterface + */ + public function setProduct(ProductInterface $product) + { + $this->product = $product; + } + + /** + * @return string + */ + public function getSource() + { + return $this->source; + } + + /** + * @param string + */ + public function setSource($source) + { + $this->source = $source; + } +} diff --git a/src/AntiMattr/Common/Product/ImageInterface.php b/src/AntiMattr/Common/Product/ImageInterface.php new file mode 100755 index 0000000..11c0564 --- /dev/null +++ b/src/AntiMattr/Common/Product/ImageInterface.php @@ -0,0 +1,62 @@ + + */ +interface ImageInterface +{ + /** + * @return string + */ + public function getId(); + + /** + * @param string + */ + public function setId($id); + + /** + * @return ArrayAccess + */ + public function getMeta(); + + /** + * @param ArrayAccess + */ + public function setMeta(ArrayAccess $meta); + + /** + * @return int + */ + public function getPosition(); + + /** + * @param int + * + * @throws InvalidArgumentException + */ + public function setPosition($position); + + /** + * @return string + */ + public function getSource(); + + /** + * @param string + */ + public function setSource($source); +} diff --git a/src/AntiMattr/Common/Product/Meta.php b/src/AntiMattr/Common/Product/Meta.php new file mode 100755 index 0000000..c83ef73 --- /dev/null +++ b/src/AntiMattr/Common/Product/Meta.php @@ -0,0 +1,47 @@ + + */ +class Meta implements ArrayAccess +{ + /** @var array */ + protected $container = array(); + + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + public function offsetExists($offset) + { + return isset($this->container[$offset]); + } + + public function offsetUnset($offset) + { + unset($this->container[$offset]); + } + + public function offsetGet($offset) + { + return isset($this->container[$offset]) ? $this->container[$offset] : null; + } +} diff --git a/src/AntiMattr/Common/Product/Option.php b/src/AntiMattr/Common/Product/Option.php new file mode 100755 index 0000000..847cd17 --- /dev/null +++ b/src/AntiMattr/Common/Product/Option.php @@ -0,0 +1,132 @@ + + */ +class Option implements OptionInterface +{ + /** @var AntiMattr\Common\Product\AttributeInterface */ + protected $attribute; + + /** @var ArrayAccess */ + protected $meta; + + /** @var string */ + protected $value; + + /** @var AntiMattr\Common\Product\VariationInterface */ + protected $variation; + + public function __construct() + { + $this->meta = new Meta(); + } + + /** + * @return AntiMattr\Common\Product\AttributeInterface + */ + public function getAttribute() + { + return $this->attribute; + } + + /** + * @param AntiMattr\Common\Product\AttributeInterface + */ + public function setAttribute(AttributeInterface $attribute) + { + $this->attribute = $attribute; + } + + /** + * Alphanumeric underscore + * + * @return string + */ + public function getCanonicalValue() + { + $value = (string) $this->value; + $value = strtolower($value); + // Keep Alphanumeric, dashes and underscores + $value = preg_replace("/[^a-z0-9_\s-]/", "", $value); + // Remove repeating whitespace or underscores + $value = preg_replace("/[\s_]+/", " ", $value); + // Convert whitespaces and dash to underscore + return preg_replace("/[\s-]/", "_", $value); + } + + /** + * @return ArrayAccess + */ + public function getMeta() + { + return $this->meta; + } + + /** + * @param ArrayAccess + */ + public function setMeta(ArrayAccess $meta) + { + $this->meta = $meta; + } + + /** + * Alphanumeric underscore + * + * @return string + */ + public function getUniqueIdentifier() + { + return sprintf( + "%s_%s", + $this->attribute->getCanonicalName(), + $this->getCanonicalValue() + ); + } + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * @param string + */ + public function setValue($value) + { + $this->value = $value; + } + + /** + * @return AntiMattr\Common\Product\VariationInterface + */ + public function getVariation() + { + return $this->variation; + } + + /** + * @param AntiMattr\Common\Product\VariationInterface + */ + public function setVariation(VariationInterface $variation) + { + $this->variation = $variation; + } +} diff --git a/src/AntiMattr/Common/Product/OptionInterface.php b/src/AntiMattr/Common/Product/OptionInterface.php new file mode 100755 index 0000000..feadcc9 --- /dev/null +++ b/src/AntiMattr/Common/Product/OptionInterface.php @@ -0,0 +1,74 @@ + + */ +interface OptionInterface +{ + /** + * @return AntiMattr\Common\Product\AttributeInterface + */ + public function getAttribute(); + + /** + * @param AntiMattr\Common\Product\AttributeInterface + */ + public function setAttribute(AttributeInterface $attribute); + + /** + * Alphanumeric underscore + * + * @return string + */ + public function getCanonicalValue(); + + /** + * @return ArrayAccess + */ + public function getMeta(); + + /** + * @param ArrayAccess + */ + public function setMeta(ArrayAccess $meta); + + /** + * Alphanumeric underscore + * + * @return string + */ + public function getUniqueIdentifier(); + + /** + * @return string + */ + public function getValue(); + + /** + * @param string + */ + public function setValue($value); + + /** + * @return AntiMattr\Common\Product\VariationInterface + */ + public function getVariation(); + + /** + * @param AntiMattr\Common\Product\VariationInterface + */ + public function setVariation(VariationInterface $variation); +} diff --git a/src/AntiMattr/Common/Product/Product.php b/src/AntiMattr/Common/Product/Product.php new file mode 100755 index 0000000..87a36b2 --- /dev/null +++ b/src/AntiMattr/Common/Product/Product.php @@ -0,0 +1,682 @@ + + */ +class Product implements ProductInterface +{ + /** @var Doctrine\Common\Collections\Collection */ + protected $attributes; + + /** @var DateTime */ + protected $createdAt; + + /** @var string */ + protected $currency; + + /** @var string */ + protected $description; + + /** @var string */ + protected $dimensionUnit; + + /** @var string */ + protected $ean; + + /** @var int */ + protected $height; + + /** @var string */ + protected $id; + + /** @var Doctrine\Common\Collections\Collection */ + protected $images; + + /** @var int */ + protected $length; + + /** @var ArrayAccess */ + protected $meta; + + /** @var string */ + protected $mpn; + + /** @var int */ + protected $msrp; + + /** @var int */ + protected $price; + + /** @var DateTime */ + protected $publishedAt; + + /** @var int */ + protected $quantity; + + /** @var string */ + protected $sku; + + /** @var string */ + protected $status; + + /** @var string */ + protected $title; + + /** @var string */ + protected $upc; + + /** @var DateTime */ + protected $updatedAt; + + /** @var Doctrine\Common\Collections\Collection */ + protected $variations; + + /** @var int */ + protected $weight; + + /** @var string */ + protected $weightUnit; + + /** @var int */ + protected $width; + + public function __construct() + { + $this->attributes = new ArrayCollection(); + $this->images = new ArrayCollection(); + $this->meta = new Meta(); + $this->variations = new ArrayCollection(); + } + + /** + * @param AntiMattr\Common\Product\AttributeInterface + */ + public function addAttribute(AttributeInterface $attribute) + { + $this->attributes->add($attribute); + } + + /** + * @return Doctrine\Common\Collections\Collection + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * @param AntiMattr\Common\Product\AttributeInterface + * + * @return bool + */ + public function hasAttribute(AttributeInterface $attribute) + { + return $this->attributes->contains($attribute); + } + + /** + * @param AntiMattr\Common\Product\AttributeInterface + * + * @throws OutOfBoundsException + */ + public function removeAttribute(AttributeInterface $attribute) + { + $success = $this->attributes->removeElement($attribute); + + if (!$success) { + throw new OutOfBoundsException('Product::attributes do not contain attribute to remove'); + } + } + + /** + * @param Doctrine\Common\Collections\Collection + */ + public function setAttributes(Collection $attributes) + { + $this->attributes = $attributes; + } + + /** + * @return DateTime + */ + public function getCreatedAt() + { + return $this->createdAt; + } + + /** + * @param DateTime + */ + public function setCreatedAt(DateTime $createdAt) + { + $this->createdAt = $createdAt; + } + + /** + * @return string + */ + public function getCurrency() + { + return $this->currency; + } + + /** + * @param string + */ + public function setCurrency($currency) + { + $this->currency = $currency; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * @return string + */ + public function getDimensionUnit() + { + return $this->dimensionUnit; + } + + /** + * @param string + */ + public function setDimensionUnit($dimensionUnit) + { + $this->dimensionUnit = $dimensionUnit; + } + + /** + * @return string + */ + public function getEan() + { + return $this->ean; + } + + /** + * @param string + */ + public function setEan($ean) + { + $this->ean = $ean; + } + + /** + * @return int + */ + public function getHeight() + { + return $this->height; + } + + /** + * @param int + * + * @throws InvalidArgumentException + */ + public function setHeight($height) + { + if (!is_int($height)) { + throw new InvalidArgumentException('Product::height must be an integer'); + } + + $this->height = $height; + } + + /** + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * @param string + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @param AntiMattr\Common\Product\ImageInterface + */ + public function addImage(ImageInterface $image) + { + $this->images->add($image); + if ($this !== $image->getProduct()) { + $image->setProduct($this); + } + } + + /** + * @return Doctrine\Common\Collections\Collection + */ + public function getImages() + { + return $this->images; + } + + /** + * @param AntiMattr\Common\Product\ImageInterface + * + * @return bool + */ + public function hasImage(ImageInterface $image) + { + return $this->images->contains($image); + } + + /** + * @param AntiMattr\Common\Product\ImageInterface + * + * @throws OutOfBoundsException + */ + public function removeImage(ImageInterface $image) + { + $success = $this->images->removeElement($image); + + if (!$success) { + throw new OutOfBoundsException('Product::images do not contain image to remove'); + } + } + + /** + * @param Doctrine\Common\Collections\Collection + */ + public function setImages(Collection $images) + { + $this->images = $images; + } + + /** + * @return int + */ + public function getLength() + { + return $this->length; + } + + /** + * @param int + * + * @throws InvalidArgumentException + */ + public function setLength($length) + { + if (!is_int($length)) { + throw new InvalidArgumentException('Product::length must be an integer'); + } + + $this->length = $length; + } + + /** + * @return ArrayAccess + */ + public function getMeta() + { + return $this->meta; + } + + /** + * @param ArrayAccess + */ + public function setMeta(ArrayAccess $meta) + { + $this->meta = $meta; + } + + /** + * @return string + */ + public function getMpn() + { + return $this->mpn; + } + + /** + * @param string + */ + public function setMpn($mpn) + { + $this->mpn = $mpn; + } + + /** + * @return int + */ + public function getMsrp() + { + return $this->msrp; + } + + /** + * @param int + * + * @throws InvalidArgumentException + */ + public function setMsrp($msrp) + { + if (!is_int($msrp)) { + throw new InvalidArgumentException('Product::msrp must be an integer'); + } + + $this->msrp = $msrp; + } + + /** + * @return int + */ + public function getPrice() + { + return $this->price; + } + + /** + * @param int + * + * @throws InvalidArgumentException + */ + public function setPrice($price) + { + if (!is_int($price)) { + throw new InvalidArgumentException('Product::price must be an integer'); + } + + $this->price = $price; + } + + /** + * @return DateTime + */ + public function getPublishedAt() + { + return $this->publishedAt; + } + + /** + * @param DateTime + */ + public function setPublishedAt(DateTime $publishedAt) + { + $this->publishedAt = $publishedAt; + } + + /** + * @return int + */ + public function getQuantity() + { + return $this->quantity; + } + + /** + * @param int + * + * @throws InvalidArgumentException + */ + public function setQuantity($quantity) + { + if (!is_int($quantity)) { + throw new InvalidArgumentException('Product::quantity must be an integer'); + } + + $this->quantity = $quantity; + } + + /** + * @return string + */ + public function getSku() + { + return $this->sku; + } + + /** + * @param string + */ + public function setSku($sku) + { + $this->sku = $sku; + } + + /** + * @return string + */ + public function getStatus() + { + return $this->status; + } + + /** + * @param string + */ + public function setStatus($status) + { + $this->status = $status; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * @return string + */ + public function getUpc() + { + return $this->upc; + } + + /** + * @param string + */ + public function setUpc($upc) + { + $this->upc = $upc; + } + + /** + * @return DateTime + */ + public function getUpdatedAt() + { + return $this->updatedAt; + } + + /** + * @param DateTime + */ + public function setUpdatedAt(DateTime $updatedAt) + { + $this->updatedAt = $updatedAt; + } + + /** + * @param AntiMattr\Common\Product\VariationInterface + * + * @throws OutOfBoundsException + */ + public function addVariation(VariationInterface $variation) + { + if (false === $this->isVariationUnique($variation)) { + $message = sprintf( + "Product::variations already contain a Variation with a unique identifier %s", + $variation->getUniqueIdentifier() + ); + throw new OutOfBoundsException($message); + } + + $this->variations->add($variation); + if ($this !== $variation->getProduct()) { + $variation->setProduct($this); + } + } + + /** + * @return Doctrine\Common\Collections\Collection + */ + public function getVariations() + { + return $this->variations; + } + + /** + * @param AntiMattr\Common\Product\VariationInterface + * + * @return bool + */ + public function hasVariation(VariationInterface $variation) + { + return $this->variations->contains($variation); + } + + /** + * @param AntiMattr\Common\Product\VariationInterface + * + * @return bool + */ + public function isVariationUnique(VariationInterface $variation) + { + if ($this->variations->isEmpty()) { + return true; + } + + return $this->variations->forAll(function ($key, $element) use ($variation) { + if ($variation->getUniqueIdentifier() === $element->getUniqueIdentifier() && $variation !== $element) { + return false; + } + + return true; + }); + } + + /** + * @param AntiMattr\Common\Product\VariationInterface + * + * @throws OutOfBoundsException + */ + public function removeVariation(VariationInterface $variation) + { + $success = $this->variations->removeElement($variation); + + if (!$success) { + throw new OutOfBoundsException('Product::variations do not contain variation to remove'); + } + } + + /** + * @param Doctrine\Common\Collections\Collection + */ + public function setVariations(Collection $variations) + { + $this->variations = $variations; + } + + /** + * @return int + */ + public function getWeight() + { + return $this->weight; + } + + /** + * @param int + * + * @throws InvalidArgumentException + */ + public function setWeight($weight) + { + if (!is_int($weight)) { + throw new InvalidArgumentException('Product::weight must be an integer'); + } + + $this->weight = $weight; + } + + /** + * @return string + */ + public function getWeightUnit() + { + return $this->weightUnit; + } + + /** + * @param string + */ + public function setWeightUnit($weightUnit) + { + $this->weightUnit = $weightUnit; + } + + /** + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * @param int + * + * @throws InvalidArgumentException + */ + public function setWidth($width) + { + if (!is_int($width)) { + throw new InvalidArgumentException('Product::width must be an integer'); + } + + $this->width = $width; + } +} diff --git a/src/AntiMattr/Common/Product/ProductInterface.php b/src/AntiMattr/Common/Product/ProductInterface.php new file mode 100755 index 0000000..4a414ec --- /dev/null +++ b/src/AntiMattr/Common/Product/ProductInterface.php @@ -0,0 +1,312 @@ + + */ +interface ProductInterface +{ + /** + * @param AntiMattr\Common\Product\AttributeInterface + */ + public function addAttribute(AttributeInterface $attribute); + + /** + * @return Doctrine\Common\Collections\Collection + */ + public function getAttributes(); + + /** + * @param AntiMattr\Common\Product\AttributeInterface + * + * @return bool + */ + public function hasAttribute(AttributeInterface $attribute); + + /** + * @param AntiMattr\Common\Product\AttributeInterface + * + * @throws OutOfBoundsException + */ + public function removeAttribute(AttributeInterface $attribute); + + /** + * @param Doctrine\Common\Collections\Collection + */ + public function setAttributes(Collection $attributes); + + /** + * @return DateTime + */ + public function getCreatedAt(); + + /** + * @param DateTime + */ + public function setCreatedAt(DateTime $createdAt); + + /** + * @return string + */ + public function getDescription(); + + /** + * @param string + */ + public function setDescription($description); + + /** + * @return string + */ + public function getDimensionUnit(); + /** + * @param string + */ + public function setDimensionUnit($dimensionUnit); + + /** + * @return string + */ + public function getEan(); + + /** + * @param string + */ + public function setEan($ean); + + /** + * @return int + */ + public function getHeight(); + + /** + * @param int + */ + public function setHeight($height); + + /** + * @return string + */ + public function getId(); + + /** + * @param string + */ + public function setId($id); + + /** + * @param AntiMattr\Common\Product\ImageInterface + */ + public function addImage(ImageInterface $image); + + /** + * @return Doctrine\Common\Collections\Collection + */ + public function getImages(); + + /** + * @param AntiMattr\Common\Product\ImageInterface + * + * @return bool + */ + public function hasImage(ImageInterface $image); + + /** + * @param AntiMattr\Common\Product\ImageInterface + * + * @throws OutOfBoundsException + */ + public function removeImage(ImageInterface $image); + + /** + * @param Doctrine\Common\Collections\Collection + */ + public function setImages(Collection $images); + + /** + * @return int + */ + public function getLength(); + + /** + * @param int + */ + public function setLength($length); + + /** + * @return ArrayAccess + */ + public function getMeta(); + + /** + * @param ArrayAccess + */ + public function setMeta(ArrayAccess $meta); + + /** + * @return string + */ + public function getMpn(); + + /** + * @param string + */ + public function setMpn($mpn); + + /** + * @return int + */ + public function getMsrp(); + + /** + * @param int + * + * @throws InvalidArgumentException + */ + public function setMsrp($msrp); + + /** + * @return int + */ + public function getPrice(); + + /** + * @param int + * + * @throws InvalidArgumentException + */ + public function setPrice($price); + + /** + * @return DateTime + */ + public function getPublishedAt(); + + /** + * @param DateTime + */ + public function setPublishedAt(DateTime $publishedAt); + + /** + * @return int + */ + public function getQuantity(); + + /** + * @param int + */ + public function setQuantity($quantity); + + /** + * @return string + */ + public function getSku(); + + /** + * @param string + */ + public function setSku($sku); + + /** + * @return string + */ + public function getTitle(); + + /** + * @param string + */ + public function setTitle($title); + + /** + * @return string + */ + public function getUpc(); + + /** + * @param string + */ + public function setUpc($upc); + + /** + * @return DateTime + */ + public function getUpdatedAt(); + + /** + * @param DateTime + */ + public function setUpdatedAt(DateTime $updatedAt); + + /** + * @param AntiMattr\Common\Product\VariationInterface + */ + public function addVariation(VariationInterface $variant); + + /** + * @return Doctrine\Common\Collections\Collection + */ + public function getVariations(); + + /** + * @param AntiMattr\Common\Product\VariationInterface + * + * @return bool + */ + public function hasVariation(VariationInterface $variation); + + /** + * @param AntiMattr\Common\Product\VariationInterface + * + * @throws OutOfBoundsException + */ + public function removeVariation(VariationInterface $variation); + + /** + * @param Doctrine\Common\Collections\Collection + */ + public function setVariations(Collection $variants); + + /** + * @return int + */ + public function getWeight(); + + /** + * @param int + */ + public function setWeight($weight); + + /** + * @return string + */ + public function getWeightUnit(); + + /** + * @param string + */ + public function setWeightUnit($weightUnit); + + /** + * @return int + */ + public function getWidth(); + + /** + * @param int + */ + public function setWidth($width); +} diff --git a/src/AntiMattr/Common/Product/Variation.php b/src/AntiMattr/Common/Product/Variation.php new file mode 100755 index 0000000..ed378df --- /dev/null +++ b/src/AntiMattr/Common/Product/Variation.php @@ -0,0 +1,169 @@ + + */ +class Variation extends Product implements VariationInterface +{ + /** @var AntiMattr\Common\Product\ImageInterface */ + protected $image; + + /** @var Doctrine\Common\Collections\Collection */ + protected $options; + + /** @var int */ + protected $position; + + /** @var AntiMattr\Common\Product\ProductInterface */ + protected $product; + + public function __construct() + { + parent::__construct(); + $this->options = new ArrayCollection(); + } + + /** + * @return AntiMattr\Common\Product\ImageInterface + */ + public function getImage() + { + return $this->image; + } + + /** + * @param AntiMattr\Common\Product\ImageInterface + */ + public function setImage(ImageInterface $image) + { + $this->image = $image; + if ($this->product && !$this->product->hasImage($image)) { + $this->product->addImage($image); + } + } + + /** + * @param AntiMattr\Common\Product\OptionInterface + */ + public function addOption(OptionInterface $option) + { + $this->options->add($option); + if ($this !== $option->getVariation()) { + $option->setVariation($this); + } + } + + /** + * @return Doctrine\Common\Collections\Collection + */ + public function getOptions() + { + return $this->options; + } + + /** + * @param AntiMattr\Common\Product\OptionInterface + * + * @return bool + */ + public function hasOption(OptionInterface $option) + { + return $this->options->contains($option); + } + + /** + * @param AntiMattr\Common\Product\OptionInterface + * + * @throws OutOfBoundsException + */ + public function removeOption(OptionInterface $option) + { + $success = $this->options->removeElement($option); + + if (!$success) { + throw new OutOfBoundsException('Variation::options do not contain option to remove'); + } + } + + /** + * @param Doctrine\Common\Collections\Collection + */ + public function setOptions(Collection $options) + { + $this->options = $options; + } + + /** + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * @param int + * + * @throws InvalidArgumentException + */ + public function setPosition($position) + { + if (!is_int($position)) { + throw new InvalidArgumentException('Variant::position must be an integer'); + } + + $this->position = $position; + } + + /** + * @return AntiMattr\Common\Product\ProductInterface + */ + public function getProduct() + { + return $this->product; + } + + /** + * @param AntiMattr\Common\Product\ProductInterface + */ + public function setProduct(ProductInterface $product) + { + $this->product = $product; + } + + /** + * A Variation is uniquely identified by the combination of Variation::options + * + * @return string + */ + public function getUniqueIdentifier() + { + if ($this->options->isEmpty()) { + return; + } + + $keys = array(); + foreach ($this->options as $option) { + $keys[] = $option->getUniqueIdentifier(); + } + sort($keys); + + return implode("_", $keys); + } +} diff --git a/src/AntiMattr/Common/Product/VariationInterface.php b/src/AntiMattr/Common/Product/VariationInterface.php new file mode 100755 index 0000000..74d5649 --- /dev/null +++ b/src/AntiMattr/Common/Product/VariationInterface.php @@ -0,0 +1,88 @@ + + */ +interface VariationInterface +{ + /** + * @return AntiMattr\Common\Product\ImageInterface + */ + public function getImage(); + + /** + * @param AntiMattr\Common\Product\ImageInterface + */ + public function setImage(ImageInterface $image); + + /** + * @param AntiMattr\Common\Product\OptionInterface + */ + public function addOption(OptionInterface $option); + + /** + * @return Doctrine\Common\Collections\Collection + */ + public function getOptions(); + + /** + * @param AntiMattr\Common\Product\OptionInterface + * + * @return bool + */ + public function hasOption(OptionInterface $option); + + /** + * @param AntiMattr\Common\Product\OptionInterface + * + * @throws OutOfBoundsException + */ + public function removeOption(OptionInterface $option); + + /** + * @param Doctrine\Common\Collections\Collection + */ + public function setOptions(Collection $options); + + /** + * @return int + */ + public function getPosition(); + + /** + * @param int + * + * @throws InvalidArgumentException + */ + public function setPosition($position); + + /** + * @return AntiMattr\Common\Product\ProductInterface + */ + public function getProduct(); + + /** + * @param AntiMattr\Common\Product\ProductInterface + */ + public function setProduct(ProductInterface $product); + + /** + * A Variation is uniquely identified by the combination of options + * + * @return string + */ + public function getUniqueIdentifier(); +} diff --git a/tests/AntiMattr/Tests/Common/Product/AttibuteTest.php b/tests/AntiMattr/Tests/Common/Product/AttibuteTest.php new file mode 100755 index 0000000..1464f96 --- /dev/null +++ b/tests/AntiMattr/Tests/Common/Product/AttibuteTest.php @@ -0,0 +1,88 @@ +attribute = new Attribute(); + } + + public function testConstructor() + { + $this->assertInstanceOf('AntiMattr\Common\Product\AttributeInterface', $this->attribute); + $this->assertEquals('', $this->attribute->getCanonicalName()); + $this->assertNull($this->attribute->getId()); + $this->assertNotNull($this->attribute->getMeta()); + $this->assertNull($this->attribute->getName()); + $this->assertNotNull($this->attribute->getOptions()); + } + + public function testSettersGetters() + { + $id = 'id'; + $this->attribute->setId($id); + $this->assertEquals($id, $this->attribute->getId()); + + $meta = $this->getMock('ArrayAccess'); + $this->attribute->setMeta($meta); + $this->assertEquals($meta, $this->attribute->getMeta()); + + $name = 'name'; + $this->attribute->setName($name); + $this->assertEquals($name, $this->attribute->getName()); + + $options = $this->getMock('Doctrine\Common\Collections\Collection'); + $this->attribute->setOptions($options); + $this->assertEquals($options, $this->attribute->getOptions()); + + $option = $this->buildMock('AntiMattr\Common\Product\Option'); + $options->expects($this->once()) + ->method('contains') + ->with($option) + ->will($this->returnValue(true)); + $this->assertTrue($this->attribute->hasOption($option)); + + $options->expects($this->once()) + ->method('removeElement') + ->with($option) + ->will($this->returnValue(true)); + $this->attribute->removeOption($option); + + $option2 = $this->buildMock('AntiMattr\Common\Product\Option'); + + $option2->expects($this->once()) + ->method('getAttribute') + ->will($this->returnValue(null)); + + $option2->expects($this->once()) + ->method('setAttribute') + ->with($this->attribute); + + $this->attribute->addOption($option2); + } + + /** + * @expectedException OutOfBoundsException + */ + public function testRemoveUnknownOptionThrowsOutOfBoundsException() + { + $element = $this->buildMock('AntiMattr\Common\Product\Option'); + $this->attribute->removeOption($element); + } + + public function testGetCanonicalName() + { + $name = "ColOR+++! 33"; + $this->attribute->setName($name); + + $expectedName = 'color_33'; + $this->assertEquals($expectedName, $this->attribute->getCanonicalName()); + } +} diff --git a/tests/AntiMattr/Tests/Common/Product/ImageTest.php b/tests/AntiMattr/Tests/Common/Product/ImageTest.php new file mode 100755 index 0000000..5a046d7 --- /dev/null +++ b/tests/AntiMattr/Tests/Common/Product/ImageTest.php @@ -0,0 +1,59 @@ +image = new Image(); + } + + public function testConstructor() + { + $this->assertInstanceOf('AntiMattr\Common\Product\ImageInterface', $this->image); + $this->assertNull($this->image->getId()); + $this->assertNotNull($this->image->getMeta()); + $this->assertNull($this->image->getPosition()); + $this->assertNull($this->image->getProduct()); + $this->assertNull($this->image->getSource()); + } + + public function testSettersGetters() + { + $id = 'id'; + $this->image->setId($id); + $this->assertEquals($id, $this->image->getId()); + + $meta = $this->getMock('ArrayAccess'); + $this->image->setMeta($meta); + $this->assertEquals($meta, $this->image->getMeta()); + + $position = 1; + $this->image->setPosition($position); + $this->assertEquals($position, $this->image->getPosition()); + + $product = $this->buildMock('AntiMattr\Common\Product\Product'); + $this->image->setProduct($product); + + $this->assertEquals($product, $this->image->getProduct()); + + $source = 'http://example.com/product.jpg'; + $this->image->setSource($source); + $this->assertEquals($source, $this->image->getSource()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetPositionWithDecimalThrowsInvalidArgumentException() + { + $decimal = 100.00; + $this->image->setPosition($decimal); + } +} diff --git a/tests/AntiMattr/Tests/Common/Product/MetaTest.php b/tests/AntiMattr/Tests/Common/Product/MetaTest.php new file mode 100755 index 0000000..9c4f970 --- /dev/null +++ b/tests/AntiMattr/Tests/Common/Product/MetaTest.php @@ -0,0 +1,21 @@ +meta = new Meta(); + } + + public function testConstructor() + { + $this->assertInstanceOf('ArrayAccess', $this->meta); + } +} diff --git a/tests/AntiMattr/Tests/Common/Product/OptionTest.php b/tests/AntiMattr/Tests/Common/Product/OptionTest.php new file mode 100755 index 0000000..433be63 --- /dev/null +++ b/tests/AntiMattr/Tests/Common/Product/OptionTest.php @@ -0,0 +1,71 @@ +option = new Option(); + } + + public function testConstructor() + { + $this->assertInstanceOf('AntiMattr\Common\Product\OptionInterface', $this->option); + $this->assertNull($this->option->getAttribute()); + $this->assertNotNull($this->option->getMeta()); + $this->assertNull($this->option->getValue()); + $this->assertEquals('', $this->option->getCanonicalValue()); + $this->assertNull($this->option->getVariation()); + } + + public function testSettersGetters() + { + $attribute = $this->buildMock('AntiMattr\Common\Product\Attribute'); + $this->option->setAttribute($attribute); + $this->assertEquals($attribute, $this->option->getAttribute()); + + $meta = $this->getMock('ArrayAccess'); + $this->option->setMeta($meta); + $this->assertEquals($meta, $this->option->getMeta()); + + $value = 'value'; + $this->option->setValue($value); + $this->assertEquals($value, $this->option->getValue()); + + $variation = $this->buildMock('AntiMattr\Common\Product\Variation'); + $this->option->setVariation($variation); + $this->assertEquals($variation, $this->option->getVariation()); + } + + public function testGetCanonicalValue() + { + $value = "ReD+++! 11"; + $this->option->setValue($value); + + $expectedValue = 'red_11'; + $this->assertEquals($expectedValue, $this->option->getCanonicalValue()); + } + + public function testGetUniqueIdentifier() + { + $expectedIdentifier = "foo1_bar1"; + + $attribute = $this->buildMock('AntiMattr\Common\Product\Attribute'); + $this->option->setAttribute($attribute); + + $attribute->expects($this->once()) + ->method('getCanonicalName') + ->will($this->returnValue('foo1')); + + $value = 'BaR1!'; + $this->option->setValue($value); + + $this->assertEquals($expectedIdentifier, $this->option->getUniqueIdentifier()); + } +} diff --git a/tests/AntiMattr/Tests/Common/Product/ProductTest.php b/tests/AntiMattr/Tests/Common/Product/ProductTest.php new file mode 100755 index 0000000..dc32920 --- /dev/null +++ b/tests/AntiMattr/Tests/Common/Product/ProductTest.php @@ -0,0 +1,394 @@ +product = new Product(); + } + + public function testConstructor() + { + $this->assertInstanceOf('AntiMattr\Common\Product\ProductInterface', $this->product); + $this->assertNotNull($this->product->getAttributes()); + $this->assertNull($this->product->getCreatedAt()); + $this->assertNull($this->product->getCurrency()); + $this->assertNull($this->product->getDescription()); + $this->assertNull($this->product->getDimensionUnit()); + $this->assertNull($this->product->getEan()); + $this->assertNull($this->product->getHeight()); + $this->assertNull($this->product->getId()); + $this->assertNotNull($this->product->getImages()); + $this->assertNull($this->product->getLength()); + $this->assertNotNull($this->product->getMeta()); + $this->assertNull($this->product->getMsrp()); + $this->assertNull($this->product->getPrice()); + $this->assertNull($this->product->getPublishedAt()); + $this->assertNull($this->product->getQuantity()); + $this->assertNull($this->product->getSku()); + $this->assertNull($this->product->getStatus()); + $this->assertNull($this->product->getTitle()); + $this->assertNull($this->product->getUpc()); + $this->assertNull($this->product->getUpdatedAt()); + $this->assertNotNull($this->product->getVariations()); + $this->assertNull($this->product->getWeight()); + $this->assertNull($this->product->getWeightUnit()); + $this->assertNull($this->product->getWidth()); + } + + public function testSettersGetters() + { + $attributes = $this->getMock('Doctrine\Common\Collections\Collection'); + $this->product->setAttributes($attributes); + $this->assertEquals($attributes, $this->product->getAttributes()); + + $attribute = $this->buildMock('AntiMattr\Common\Product\Attribute'); + $attributes->expects($this->once()) + ->method('contains') + ->with($attribute) + ->will($this->returnValue(true)); + $this->assertTrue($this->product->hasAttribute($attribute)); + + $attributes->expects($this->once()) + ->method('removeElement') + ->with($attribute) + ->will($this->returnValue(true)); + $this->product->removeAttribute($attribute); + + $createdAt = $this->createDateTime(); + $this->product->setCreatedAt($createdAt); + $this->assertEquals($createdAt, $this->product->getCreatedAt()); + + $currency = 'USD'; + $this->product->setCurrency($currency); + $this->assertEquals($currency, $this->product->getCurrency()); + + $description = 'description'; + $this->product->setDescription($description); + $this->assertEquals($description, $this->product->getDescription()); + + $dimensionUnit = 'dimensionUnit'; + $this->product->setDimensionUnit($dimensionUnit); + $this->assertEquals($dimensionUnit, $this->product->getDimensionUnit()); + + $ean = 'ean'; + $this->product->setEan($ean); + $this->assertEquals($ean, $this->product->getEan()); + + $height = 2; + $this->product->setHeight($height); + $this->assertEquals($height, $this->product->getHeight()); + + $id = 'id'; + $this->product->setId($id); + $this->assertEquals($id, $this->product->getId()); + + $images = $this->getMock('Doctrine\Common\Collections\Collection'); + $this->product->setImages($images); + $this->assertEquals($images, $this->product->getImages()); + + $image = $this->buildMock('AntiMattr\Common\Product\Image'); + $images->expects($this->once()) + ->method('contains') + ->with($image) + ->will($this->returnValue(true)); + $this->assertTrue($this->product->hasImage($image)); + + $images->expects($this->once()) + ->method('removeElement') + ->with($image) + ->will($this->returnValue(true)); + $this->product->removeImage($image); + + $image2 = $this->buildMock('AntiMattr\Common\Product\Image'); + + $image2->expects($this->once()) + ->method('getProduct') + ->will($this->returnValue(null)); + + $image2->expects($this->once()) + ->method('setProduct') + ->with($this->product); + + $this->product->addImage($image2); + + $length = 3; + $this->product->setLength($length); + $this->assertEquals($length, $this->product->getLength()); + + $meta = $this->getMock('ArrayAccess'); + $this->product->setMeta($meta); + $this->assertEquals($meta, $this->product->getMeta()); + + $mpn = 'mpn'; + $this->product->setMpn($mpn); + $this->assertEquals($mpn, $this->product->getMpn()); + + $msrp = 88; + $this->product->setMsrp($msrp); + $this->assertEquals($msrp, $this->product->getMsrp()); + + $price = 100; + $this->product->setPrice($price); + $this->assertEquals($price, $this->product->getPrice()); + + $publishedAt = $this->createDateTime(); + $this->product->setPublishedAt($publishedAt); + $this->assertEquals($publishedAt, $this->product->getPublishedAt()); + + $quantity = 10; + $this->product->setQuantity($quantity); + $this->assertEquals($quantity, $this->product->getQuantity()); + + $sku = 'sku'; + $this->product->setSku($sku); + $this->assertEquals($sku, $this->product->getSku()); + + $status = 'status'; + $this->product->setStatus($status); + $this->assertEquals($status, $this->product->getStatus()); + + $title = 'title'; + $this->product->setTitle($title); + $this->assertEquals($title, $this->product->getTitle()); + + $upc = 'upc'; + $this->product->setUpc($upc); + $this->assertEquals($upc, $this->product->getUpc()); + + $updatedAt = $this->createDateTime(); + $this->product->setUpdatedAt($updatedAt); + $this->assertEquals($updatedAt, $this->product->getUpdatedAt()); + + $variations = $this->getMock('Doctrine\Common\Collections\Collection'); + $this->product->setVariations($variations); + $this->assertEquals($variations, $this->product->getVariations()); + + $variation = $this->buildMock('AntiMattr\Common\Product\Variation'); + $variations->expects($this->once()) + ->method('contains') + ->with($variation) + ->will($this->returnValue(true)); + $this->assertTrue($this->product->hasVariation($variation)); + + $variations->expects($this->once()) + ->method('removeElement') + ->with($variation) + ->will($this->returnValue(true)); + $this->product->removeVariation($variation); + + $variation2 = $this->buildMock('AntiMattr\Common\Product\Variation'); + + $variation2->expects($this->once()) + ->method('getProduct') + ->will($this->returnValue(null)); + + $variation2->expects($this->once()) + ->method('setProduct') + ->with($this->product); + + $this->product->addVariation($variation2); + + $weight = 4; + $this->product->setWeight($weight); + $this->assertEquals($weight, $this->product->getWeight()); + + $weightUnit = 'weightUnit'; + $this->product->setWeightUnit($weightUnit); + $this->assertEquals($weightUnit, $this->product->getWeightUnit()); + + $width = 5; + $this->product->setWidth($width); + $this->assertEquals($width, $this->product->getWidth()); + } + + public function testIsVariationUnique() + { + // Variations Empty + $variation1 = $this->buildMock('AntiMattr\Common\Product\Variation'); + $this->assertTrue($this->product->isVariationUnique($variation1)); + + $variation2 = new Variation(); + $variation2Option1 = $this->buildMock('AntiMattr\Common\Product\Option'); + $variation2Option2 = $this->buildMock('AntiMattr\Common\Product\Option'); + $variation2Option3 = $this->buildMock('AntiMattr\Common\Product\Option'); + $variation2->addOption($variation2Option1); + $variation2->addOption($variation2Option2); + $variation2->addOption($variation2Option3); + + $variation3 = new Variation(); + $variation3Option1 = $this->buildMock('AntiMattr\Common\Product\Option'); + $variation3Option2 = $this->buildMock('AntiMattr\Common\Product\Option'); + $variation3Option3 = $this->buildMock('AntiMattr\Common\Product\Option'); + $variation3->addOption($variation3Option1); + $variation3->addOption($variation3Option2); + $variation3->addOption($variation3Option3); + + $variation4 = new Variation(); + $variation4Option1 = $this->buildMock('AntiMattr\Common\Product\Option'); + $variation4Option2 = $this->buildMock('AntiMattr\Common\Product\Option'); + $variation4Option3 = $this->buildMock('AntiMattr\Common\Product\Option'); + $variation4->addOption($variation4Option1); + $variation4->addOption($variation4Option2); + $variation4->addOption($variation4Option3); + + $variation3Duplicate = new Variation(); + $variation3DuplicateOption1 = $this->buildMock('AntiMattr\Common\Product\Option'); + $variation3DuplicateOption2 = $this->buildMock('AntiMattr\Common\Product\Option'); + $variation3DuplicateOption3 = $this->buildMock('AntiMattr\Common\Product\Option'); + $variation3Duplicate->addOption($variation3DuplicateOption1); + $variation3Duplicate->addOption($variation3DuplicateOption2); + $variation3Duplicate->addOption($variation3DuplicateOption3); + + $variation2Option1->expects($this->any()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('color_red')); + $variation2Option2->expects($this->any()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('size_small')); + $variation2Option3->expects($this->any()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('fabric_cotton')); + + $variation3Option1->expects($this->any()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('color_yellow')); + $variation3Option2->expects($this->any()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('size_medium')); + $variation3Option3->expects($this->any()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('fabric_cotton')); + + $variation4Option1->expects($this->any()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('color_red')); + $variation4Option2->expects($this->any()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('size_medium')); + $variation4Option3->expects($this->any()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('fabric_nylon')); + + $variation3DuplicateOption1->expects($this->any()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('color_yellow')); + $variation3DuplicateOption2->expects($this->any()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('size_medium')); + $variation3DuplicateOption3->expects($this->any()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('fabric_cotton')); + + $this->product->addVariation($variation2); + $this->product->addVariation($variation3); + + // Confirm pre-existing references are affirmative + $this->assertTrue($this->product->isVariationUnique($variation2)); + $this->assertTrue($this->product->isVariationUnique($variation3)); + + // Confirm untracked and unique references are affirmative + $this->assertTrue($this->product->isVariationUnique($variation4)); + + $this->product->addVariation($variation4); + $this->assertFalse($this->product->isVariationUnique($variation3Duplicate)); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetMsrpWithDecimalThrowsInvalidArgumentException() + { + $decimal = 100.00; + $this->product->setMsrp($decimal); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetPriceWithDecimalThrowsInvalidArgumentException() + { + $decimal = 100.00; + $this->product->setPrice($decimal); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetHeightWithDecimalThrowsInvalidArgumentException() + { + $decimal = 100.00; + $this->product->setHeight($decimal); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetLengthWithDecimalThrowsInvalidArgumentException() + { + $decimal = 100.00; + $this->product->setLength($decimal); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetQuantityWithDecimalThrowsInvalidArgumentException() + { + $decimal = 100.00; + $this->product->setQuantity($decimal); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetWeightWithDecimalThrowsInvalidArgumentException() + { + $decimal = 100.00; + $this->product->setWeight($decimal); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetWidthWithDecimalThrowsInvalidArgumentException() + { + $decimal = 100.00; + $this->product->setWidth($decimal); + } + + /** + * @expectedException OutOfBoundsException + */ + public function testRemoveUnknownAttributeThrowsOutOfBoundsException() + { + $element = $this->buildMock('AntiMattr\Common\Product\Attribute'); + $this->product->removeAttribute($element); + } + + /** + * @expectedException OutOfBoundsException + */ + public function testRemoveUnknownImageThrowsOutOfBoundsException() + { + $element = $this->buildMock('AntiMattr\Common\Product\Image'); + $this->product->removeImage($element); + } + + /** + * @expectedException OutOfBoundsException + */ + public function testRemoveUnknownVariationThrowsOutOfBoundsException() + { + $element = $this->buildMock('AntiMattr\Common\Product\Variation'); + $this->product->removeVariation($element); + } +} diff --git a/tests/AntiMattr/Tests/Common/Product/VariationTest.php b/tests/AntiMattr/Tests/Common/Product/VariationTest.php new file mode 100755 index 0000000..c5fb3d4 --- /dev/null +++ b/tests/AntiMattr/Tests/Common/Product/VariationTest.php @@ -0,0 +1,126 @@ +variation = new Variation(); + } + + public function testConstructor() + { + $this->assertInstanceOf('AntiMattr\Common\Product\ProductInterface', $this->variation); + $this->assertInstanceOf('AntiMattr\Common\Product\Product', $this->variation); + $this->assertInstanceOf('AntiMattr\Common\Product\ProductInterface', $this->variation); + $this->assertNull($this->variation->getImage()); + $this->assertNotNull($this->variation->getMeta()); + $this->assertNotNull($this->variation->getOptions()); + $this->assertNull($this->variation->getPosition()); + $this->assertNull($this->variation->getProduct()); + $this->assertNull($this->variation->getUniqueIdentifier()); + } + + public function testSettersGetters() + { + $product = $this->buildMock('AntiMattr\Common\Product\Product'); + $this->variation->setProduct($product); + + $this->assertEquals($product, $this->variation->getProduct()); + + $image = $this->buildMock('AntiMattr\Common\Product\Image'); + $this->variation->setImage($image); + + $this->assertEquals($image, $this->variation->getImage()); + + $meta = $this->getMock('ArrayAccess'); + $this->variation->setMeta($meta); + $this->assertEquals($meta, $this->variation->getMeta()); + + $options = $this->getMock('Doctrine\Common\Collections\Collection'); + $this->variation->setOptions($options); + $this->assertEquals($options, $this->variation->getOptions()); + + $option = $this->buildMock('AntiMattr\Common\Product\Option'); + $options->expects($this->once()) + ->method('contains') + ->with($option) + ->will($this->returnValue(true)); + $this->assertTrue($this->variation->hasOption($option)); + + $options->expects($this->once()) + ->method('removeElement') + ->with($option) + ->will($this->returnValue(true)); + $this->variation->removeOption($option); + + $option2 = $this->buildMock('AntiMattr\Common\Product\Option'); + + $option2->expects($this->once()) + ->method('getVariation') + ->will($this->returnValue(null)); + + $option2->expects($this->once()) + ->method('setVariation') + ->with($this->variation); + + $this->variation->addOption($option2); + + $position = 1; + $this->variation->setPosition($position); + $this->assertEquals($position, $this->variation->getPosition()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetPositionWithDecimalThrowsInvalidArgumentException() + { + $decimal = 100.00; + $this->variation->setPosition($decimal); + } + + /** + * @expectedException OutOfBoundsException + */ + public function testRemoveUnknownOptionThrowsOutOfBoundsException() + { + $element = $this->buildMock('AntiMattr\Common\Product\Option'); + $this->variation->removeOption($element); + } + + public function testGetUniqueIdentifier() + { + // Regardless of option order, the identifier should be alphabetized + // This ensures options in different order, produce the same identifier + $expectedIdentifier = "foo1_bar1_foo2_bar2_foo3_bar3"; + + $option1 = $this->buildMock('AntiMattr\Common\Product\Option'); + $option2 = $this->buildMock('AntiMattr\Common\Product\Option'); + $option3 = $this->buildMock('AntiMattr\Common\Product\Option'); + + $this->variation->addOption($option1); + $this->variation->addOption($option2); + $this->variation->addOption($option3); + + $option1->expects($this->once()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('foo1_bar1')); + + $option2->expects($this->once()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('foo3_bar3')); + + $option3->expects($this->once()) + ->method('getUniqueIdentifier') + ->will($this->returnValue('foo2_bar2')); + + $this->assertEquals($expectedIdentifier, $this->variation->getUniqueIdentifier()); + } +}