├── Resources ├── views │ ├── Carts │ │ ├── deleteCart.html.twig │ │ ├── newCart.html.twig │ │ └── getCart.html.twig │ ├── Transitions │ │ └── getTransition.html.twig │ ├── layout.html.twig │ └── Items │ │ └── getItem.html.twig ├── config │ ├── validation │ │ ├── orm.xml │ │ └── mongodb.xml │ ├── calculator.xml │ ├── listener.xml │ ├── transition.xml │ ├── parameter.xml │ ├── event.xml │ ├── orm.xml │ ├── mongodb.xml │ ├── form.xml │ ├── doctrine │ │ └── model │ │ │ ├── Item.orm.xml │ │ │ ├── Item.mongodb.xml │ │ │ ├── Cart.mongodb.xml │ │ │ └── Cart.orm.xml │ └── validation.xml └── meta │ └── LICENSE ├── .gitignore ├── .travis.yml ├── Tests ├── bootstrap.php ├── TestCart.php └── DependencyInjection │ └── LeaphlyCartExtensionTest.php ├── phpunit.xml.dist ├── DependencyInjection ├── Compiler │ ├── ValidationPass.php │ └── RegisterMappingsPass.php ├── Configuration.php └── LeaphlyCartExtension.php ├── composer.json ├── Controller ├── CartTransitionsController.php ├── BaseController.php ├── CartItemsController.php └── CartsController.php ├── README.markdown └── LeaphlyCartBundle.php /Resources/views/Carts/deleteCart.html.twig: -------------------------------------------------------------------------------- 1 | deleted. 2 | -------------------------------------------------------------------------------- /Resources/views/Transitions/getTransition.html.twig: -------------------------------------------------------------------------------- 1 | {{ content }}! 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | phpunit.xml 3 | Tests/autoload.php 4 | vendor/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3.3 5 | - 5.3 6 | - 5.4 7 | - 5.5 8 | 9 | before_script: composer install --dev --prefer-source 10 | -------------------------------------------------------------------------------- /Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | register(); 10 | -------------------------------------------------------------------------------- /Resources/config/validation/orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Resources/config/validation/mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Resources/views/Carts/newCart.html.twig: -------------------------------------------------------------------------------- 1 | {% block content_header '' %} 2 | 3 | {% block content %} 4 |

{{ 'cart.new.headline'|trans({}, 'AcmeDemoBundle') }}

5 | 6 | {% if (form is defined) %} 7 |
8 | 9 | {{ form_widget(form) }} 10 | 11 | 12 |
13 | {% endif %} 14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /Resources/config/calculator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Leaphly\Cart\Calculator\PriceCalculator 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ./Tests 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ./ 16 | 17 | ./Resources 18 | ./Tests 19 | ./vendor 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Resources/config/listener.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Leaphly\Cart\Listener\PriceCalculatorListener 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Resources/config/transition.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Leaphly\Cart\Transition\FiniteTransition 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Resources/config/parameter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Leaphly\CartBundle\Controller\CartsController 9 | Leaphly\CartBundle\Controller\CartItemsController 10 | Leaphly\Cart\Handler\CartItemHandler 11 | Leaphly\Cart\Handler\CartHandler 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Tests/TestCart.php: -------------------------------------------------------------------------------- 1 | 10 | * @package Leaphly\Cart\Tests 11 | */ 12 | class TestCart extends Cart 13 | { 14 | /** 15 | * @param $id 16 | */ 17 | public function setId($id) 18 | { 19 | $this->id = $id; 20 | } 21 | 22 | /** 23 | * @return string 24 | */ 25 | public function serialize() 26 | { 27 | return 'dummy_cart'; 28 | } 29 | 30 | /** 31 | * @param string $serialized 32 | */ 33 | public function unserialize($serialized) 34 | { 35 | $data = unserialize($serialized); 36 | 37 | list( 38 | $this->expireseAt, 39 | $this->id 40 | ) = $data; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Resources/config/event.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | Leaphly\Cart\Event\CartEventFactory 10 | Leaphly\Cart\Event\ItemEventFactory 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Resources/config/orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | %leaphly_cart.model.cart.class% 11 | 12 | 13 | 14 | %leaphly_cart.model_manager_name% 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Resources/config/mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | %leaphly_cart.model.cart.class% 11 | 12 | 13 | 14 | %leaphly_cart.model_manager_name% 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Resources/config/form.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Leaphly\Cart\Form\Factory\FormFactory 9 | 10 | 11 | 12 | 13 | 14 | %leaphly_cart.model.cart.class% 15 | 16 | 17 | 18 | 19 | %leaphly_cart.model.cart.class% 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Resources/meta/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2011 Leaphly 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Resources/views/layout.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | {% if is_granted("IS_AUTHENTICATED_REMEMBERED") %} 9 | {{ 'layout.logged_in_as'|trans({'%identifier%': app.cart.identifier}, 'LeaphlyCart') }} | 10 | 11 | {{ 'layout.logout'|trans({}, 'LeaphlyCart') }} 12 | 13 | {% else %} 14 | {{ 'layout.login'|trans({}, 'LeaphlyCart') }} 15 | {% endif %} 16 |
17 | 18 | {% for type, messages in app.session.flashbag.all() %} 19 | {% for message in messages %} 20 |
21 | {{ message }} 22 |
23 | {% endfor %} 24 | {% endfor %} 25 | 26 |
27 | {% block leaphly_cart_content %} 28 | {% endblock leaphly_cart_content %} 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /Resources/views/Items/getItem.html.twig: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{ "leaply.item.name" | trans }}: {{ item.name }} 4 | 5 | {% if item.price >= 0.01 %} 6 | 7 | {{ "leaply.item.price" | trans }}: {{ item.price ~ " " ~ item.currency }} 8 | 9 | {% if item.price > item.finalPrice %} 10 | 11 | {{ "leaply.item.finalPrice" | trans }}: 12 | {{ item.finalPrice ~ " " ~ item.currency }} 13 | 14 | {% endif %} 15 | {% endif %} 16 | 25 |
26 | -------------------------------------------------------------------------------- /Resources/config/doctrine/model/Item.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Resources/config/doctrine/model/Item.mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Resources/views/Carts/getCart.html.twig: -------------------------------------------------------------------------------- 1 |
2 | {% for item in cart.items %} 3 | {{ include("LeaphlyCartBundle:Items:getItem.html.twig", { "item": item }) }} 4 | {% else %} 5 | {{ "leaply.cart.empty" | trans }} 6 | {% endfor %} 7 | {% if cart.price >= 0.01 %} 8 | 9 | {{ "leaply.cart.price" | trans }}: {{ cart.price ~ " " ~ cart.currency }} 10 | 11 | {% if cart.price > cart.finalPrice %} 12 | 13 | {{ "leaply.cart.finalPrice" | trans }}: {{ cart.finalPrice ~ " " ~ cart.currency }} 14 | 15 | {% endif %} 16 | {% endif %} 17 | {# todo: I don"t know if we need to say something about the state... -#} 18 | 29 |
30 | -------------------------------------------------------------------------------- /Resources/config/doctrine/model/Cart.mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Resources/config/doctrine/model/Cart.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/ValidationPass.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class ValidationPass implements CompilerPassInterface 15 | { 16 | /** 17 | * {@inheritDoc} 18 | */ 19 | public function process(ContainerBuilder $container) 20 | { 21 | if (!$container->hasParameter('leaphly_cart.storage')) { 22 | return; 23 | } 24 | 25 | $storage = $container->getParameter('leaphly_cart.storage'); 26 | if ('custom' === $storage) { 27 | return; 28 | } 29 | 30 | if (!$container->hasParameter('validator.mapping.loader.xml_files_loader.mapping_files')) { 31 | return; 32 | } 33 | 34 | $files = $container->getParameter('validator.mapping.loader.xml_files_loader.mapping_files'); 35 | $validationFile = __DIR__ . '/../../Resources/config/validation/' . $storage . '.xml'; 36 | 37 | if (is_file($validationFile)) { 38 | $files[] = realpath($validationFile); 39 | $container->addResource(new FileResource($validationFile)); 40 | } 41 | 42 | $container->setParameter('validator.mapping.loader.xml_files_loader.mapping_files', $files); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaphly/cart-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Cart Bundle", 5 | "keywords": ["Cart management"], 6 | "homepage": "http://leaphly.org", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Giulio De Donato", 11 | "email": "liuggio@gmail.com" 12 | }, 13 | { 14 | "name": "Leaphly Community", 15 | "homepage": "https://github.com/leaphly/CartBundle/contributors" 16 | } 17 | ], 18 | "minimum-stability": "dev", 19 | "require": { 20 | "php": ">=5.3.2", 21 | "symfony/framework-bundle": "~2", 22 | "jms/serializer-bundle": "~0", 23 | "leaphly/cart":"0.1.x-dev" 24 | }, 25 | "require-dev": { 26 | "doctrine/doctrine-fixtures-bundle": "dev-master", 27 | "phpunit/phpunit": "3.7.*", 28 | "liip/functional-test-bundle":"dev-master" 29 | }, 30 | "suggest": { 31 | "doctrine/doctrine-fixtures-bundle": "Required when using the fixture loading functionality", 32 | "doctrine/mongodb-odm-bundle": "Required when using ODM as db-driver", 33 | "doctrine/doctrine-bundle": "Required when using ORM as db-driver", 34 | "friendsofsymfony/rest-bundle": "Required if you want to use the Rest Controller" 35 | }, 36 | "autoload": { 37 | "psr-0": { "Leaphly\\CartBundle": "" } 38 | }, 39 | "extra": { 40 | "branch-alias": { 41 | "dev-master": "0.1.x-dev" 42 | } 43 | }, 44 | "target-dir": "Leaphly/CartBundle" 45 | } 46 | -------------------------------------------------------------------------------- /Controller/CartTransitionsController.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class CartTransitionsController extends FOSRestController 14 | { 15 | /** 16 | * Apply a transition to a cart. 17 | * 18 | * @ApiDoc( 19 | * resource=true 20 | * ) 21 | * 22 | * @param mixed $cart_id 23 | * @param string $transition 24 | 25 | * @return \FOS\RestBundle\View\View 26 | * 27 | * @api 28 | */ 29 | public function postTransitionAction($cart_id, $transition) 30 | { 31 | $cart = $this->get('leaphly_cart.cart_manager')->findOr404($cart_id); 32 | 33 | $this->get('leaphly_cart.cart.transition')->apply($cart, $transition, true); 34 | 35 | return $this->view($cart, 201, array()) 36 | ->setTemplate("LeaphlyCartBundle:Carts:getTransition.html.twig") 37 | ->setTemplateVar('cart'); 38 | } 39 | 40 | /** 41 | * Get the answer if is possible to apply a transaction to the cart. 42 | * 43 | * @ApiDoc( 44 | * resource=true 45 | * ) 46 | * 47 | * @param mixed $cart_id 48 | * @param string $transition 49 | 50 | * @return \FOS\RestBundle\View\View 51 | * 52 | * @api 53 | */ 54 | public function getTransitionAction($cart_id, $transition) 55 | { 56 | $cart = $this->get('leaphly_cart.cart_manager')->findOr404($cart_id); 57 | $status = 200; 58 | 59 | if (!($content = $this->get('leaphly_cart.cart.transition')->can($cart, $transition))) { 60 | $status = 406; 61 | } 62 | 63 | return $this->view($content, $status, array()) 64 | ->setTemplate("LeaphlyCartBundle:Transitions:getTransition.html.twig") 65 | ->setTemplateVar('content'); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | CartBundle 2 | ================= 3 | 4 | [![Build Status](https://secure.travis-ci.org/leaphly/CartBundle.png?branch=master)](http://travis-ci.org/leaphly/CartBundle) [![Total Downloads](https://poser.pugx.org/leaphly/cart-bundle/downloads.png)](https://packagist.org/packages/leaphly/cart-bundle) [![Latest Stable Version](https://poser.pugx.org/leaphly/cart-bundle/v/stable.png)](https://packagist.org/packages/leaphly/cart-bundle) 5 | 6 | Mission-statement 7 | ---------- 8 | 9 | The Leaphly project makes it easier for developers to add cart functionality to the Symfony2 applications or to those applications that could consume REST API. 10 | 11 | This software provides the tools and guidelines for building decoupled, high-quality and long-life e-commerce applications. 12 | 13 | [continue reading on the website](http://leaphly.org) 14 | 15 | Demo 16 | ---- 17 | 18 | [demo](http://leaphly.org/#demo) 19 | 20 | License 21 | ------- 22 | 23 | This bundle is under the MIT license. See the complete license in the bundle: 24 | 25 | Resources/meta/LICENSE 26 | 27 | Test 28 | ---- 29 | 30 | ``` bash 31 | composer.phar create-project leaphly/cart-bundle` 32 | vendor/bin/phpunit 33 | ``` 34 | 35 | All the Functional tests are into the [leaphly-sandbox](https://github.com/leaphly/leaphly-sandbox). 36 | 37 | About 38 | ----- 39 | 40 | CartBundle is a [leaphly](https://github.com/leaphly) initiative. 41 | See also the list of [contributors](https://github.com/leaphly/CartBundle/contributors). 42 | CartBundle has been inspired by the architecture of FosUserBundle. 43 | 44 | Reporting an issue or a feature request 45 | --------------------------------------- 46 | 47 | Issues and feature requests are tracked in the [Github issue tracker](https://github.com/leaphly/CartBundle/issues). 48 | 49 | When reporting a bug, it may be a good idea to reproduce it in a basic project 50 | built using the [Symfony Standard Edition](https://github.com/symfony/symfony-standard) 51 | to allow developers of the bundle to reproduce the issue by simply cloning it 52 | and following some steps. 53 | 54 | 55 | TODO 56 | ----------------------- 57 | 58 | - ~Complete the Rest controllers.~ 59 | 60 | - ~Better usage of the finite transitions.~ 61 | 62 | - ~Decouple Bundle and library.~ 63 | 64 | - Add more DB drivers 65 | 66 | -------------------------------------------------------------------------------- /LeaphlyCartBundle.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class LeaphlyCartBundle extends Bundle 17 | { 18 | public function build(ContainerBuilder $container) 19 | { 20 | parent::build($container); 21 | $container->addCompilerPass(new ValidationPass()); 22 | 23 | $this->addRegisterMappingsPass($container); 24 | } 25 | 26 | /** 27 | * @param ContainerBuilder $container 28 | */ 29 | private function addRegisterMappingsPass(ContainerBuilder $container) 30 | { 31 | // the base class is only available since symfony 2.3 32 | $symfonyVersion = class_exists('Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterMappingsPass'); 33 | 34 | $mappings = array( 35 | realpath(__DIR__ . '/Resources/config/doctrine/model') => 'Leaphly\Cart\Model', 36 | ); 37 | 38 | if ($symfonyVersion && class_exists('Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass')) { 39 | $container->addCompilerPass(DoctrineOrmMappingsPass::createXmlMappingDriver($mappings, array('leaphly_cart.model_manager_name'), 'leaphly_cart.backend_type_orm')); 40 | } else { 41 | $container->addCompilerPass(RegisterMappingsPass::createOrmMappingDriver($mappings)); 42 | } 43 | 44 | if ($symfonyVersion && class_exists('Doctrine\Bundle\MongoDBBundle\DependencyInjection\Compiler\DoctrineMongoDBMappingsPass')) { 45 | $container->addCompilerPass(DoctrineMongoDBMappingsPass::createXmlMappingDriver($mappings, array('leaphly_cart.model_manager_name'), 'leaphly_cart.backend_type_mongodb')); 46 | } else { 47 | $container->addCompilerPass(RegisterMappingsPass::createMongoDBMappingDriver($mappings)); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Resources/config/validation.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/RegisterMappingsPass.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class RegisterMappingsPass implements CompilerPassInterface 21 | { 22 | private $driver; 23 | private $driverPattern; 24 | private $namespaces; 25 | private $enabledParameter; 26 | private $fallbackManagerParameter; 27 | 28 | public function __construct($driver, $driverPattern, $namespaces, $enabledParameter, $fallbackManagerParameter) 29 | { 30 | $this->driver = $driver; 31 | $this->driverPattern = $driverPattern; 32 | $this->namespaces = $namespaces; 33 | $this->enabledParameter = $enabledParameter; 34 | $this->fallbackManagerParameter = $fallbackManagerParameter; 35 | } 36 | 37 | /** 38 | * Register mappings with the metadata drivers. 39 | * 40 | * @param ContainerBuilder $container 41 | */ 42 | public function process(ContainerBuilder $container) 43 | { 44 | if (!$container->hasParameter($this->enabledParameter)) { 45 | return; 46 | } 47 | 48 | $chainDriverDefService = $this->getChainDriverServiceName($container); 49 | $chainDriverDef = $container->getDefinition($chainDriverDefService); 50 | foreach ($this->namespaces as $namespace) { 51 | $chainDriverDef->addMethodCall('addDriver', array($this->driver, $namespace)); 52 | } 53 | } 54 | 55 | protected function getChainDriverServiceName(ContainerBuilder $container) 56 | { 57 | foreach (array('leaphly_cart.model_manager_name', $this->fallbackManagerParameter) as $param) { 58 | if ($container->hasParameter($param)) { 59 | $name = $container->getParameter($param); 60 | if ($name) { 61 | return sprintf($this->driverPattern, $name); 62 | } 63 | } 64 | } 65 | 66 | throw new ParameterNotFoundException('None of the managerParameters resulted in a valid name'); 67 | } 68 | 69 | public static function createOrmMappingDriver(array $mappings) 70 | { 71 | $arguments = array($mappings, '.orm.xml'); 72 | $locator = new Definition('Doctrine\Common\Persistence\Mapping\Driver\SymfonyFileLocator', $arguments); 73 | $driver = new Definition('Doctrine\ORM\Mapping\Driver\XmlDriver', array($locator)); 74 | 75 | return new RegisterMappingsPass($driver, 'doctrine.orm.%s_metadata_driver', $mappings, 'leaphly_cart.backend_type_orm', 'doctrine.default_entity_manager'); 76 | } 77 | 78 | public static function createMongoDBMappingDriver($mappings) 79 | { 80 | $arguments = array($mappings, '.mongodb.xml'); 81 | $locator = new Definition('Doctrine\Common\Persistence\Mapping\Driver\SymfonyFileLocator', $arguments); 82 | $driver = new Definition('Doctrine\ODM\MongoDB\Mapping\Driver\XmlDriver', array($locator)); 83 | 84 | return new RegisterMappingsPass($driver, 'doctrine_mongodb.odm.%s_metadata_driver', $mappings, 'leaphly_cart.backend_type_mongodb', 'doctrine_mongodb.odm.default_document_manager'); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Controller/BaseController.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | abstract class BaseController extends ContainerAware 21 | { 22 | protected $cartHandler; 23 | 24 | public function setCartHandler(CartHandlerInterface $cartHandler) 25 | { 26 | $this->cartHandler = $cartHandler; 27 | } 28 | /** 29 | * Create a view 30 | * 31 | * Convenience method to allow for a fluent interface. 32 | * 33 | * @param mixed $data 34 | * @param integer $statusCode 35 | * @param array $headers 36 | * 37 | * @return View 38 | */ 39 | protected function view($data = null, $statusCode = null, array $headers = array()) 40 | { 41 | return View::create($data, $statusCode, $headers); 42 | } 43 | 44 | /** 45 | * Create a Redirect view 46 | * 47 | * Convenience method to allow for a fluent interface. 48 | * 49 | * @param string $url 50 | * @param integer $statusCode 51 | * @param array $headers 52 | * 53 | * @return View 54 | */ 55 | protected function redirectView($url, $statusCode = Codes::HTTP_FOUND, array $headers = array()) 56 | { 57 | return RedirectView::create($url, $statusCode, $headers); 58 | } 59 | 60 | /** 61 | * Create a Route Redirect View 62 | * 63 | * Convenience method to allow for a fluent interface. 64 | * 65 | * @param string $route 66 | * @param mixed $parameters 67 | * @param integer $statusCode 68 | * @param array $headers 69 | * 70 | * @return View 71 | */ 72 | protected function routeRedirectView($route, array $parameters = array(), $statusCode = Codes::HTTP_CREATED, array $headers = array()) 73 | { 74 | return RouteRedirectView::create($route, $parameters, $statusCode, $headers); 75 | } 76 | 77 | /** 78 | * Convert view into a response object. 79 | * 80 | * Not necessary to use, if you are using the "ViewResponseListener", which 81 | * does this conversion automatically in kernel event "onKernelView". 82 | * 83 | * @param View $view 84 | * 85 | * @return Response 86 | */ 87 | protected function handleView(View $view) 88 | { 89 | return $this->container->get('fos_rest.view_handler')->handle($view); 90 | } 91 | 92 | /** 93 | * Create the Response. 94 | * 95 | * @param CartInterface $cart 96 | * @param int $statusCode 97 | * @param string $getCartRoute 98 | * 99 | * @return \FOS\RestBundle\View\View 100 | */ 101 | protected function createResponse(CartInterface $cart, $statusCode = 204, $getCartRoute = 'api_1_get_cart') 102 | { 103 | $headers = array(); 104 | 105 | $response = new Response(); 106 | $response->setStatusCode($statusCode); 107 | 108 | // set the `Location` header only when creating new resources 109 | if (201 === $statusCode) { 110 | $headers = array('Location'=> 111 | $this->container->get('router')->generate( 112 | $getCartRoute, array('cart_id' => $cart->getId(), '_format' => $this->container->get('request')->get('_format') ), 113 | true 114 | ) 115 | ); 116 | } 117 | 118 | return $this->view($cart, $statusCode, $headers); 119 | 120 | } 121 | 122 | /** 123 | * Fetch the Cart. 124 | * 125 | * @param mixed $cart_id 126 | * 127 | * @return CartInterface 128 | * 129 | * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException 130 | */ 131 | protected function fetchCartOr404($cart_id) 132 | { 133 | if (!($cart = $this->cartHandler->getCart($cart_id))) { 134 | throw new NotFoundHttpException($cart_id); 135 | } 136 | 137 | return $cart; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Configuration implements ConfigurationInterface 18 | { 19 | static $supportedDrivers = array('orm', 'mongodb'); 20 | /** 21 | * Generates the configuration tree. 22 | * 23 | * @return TreeBuilder 24 | */ 25 | public function getConfigTreeBuilder() 26 | { 27 | $treeBuilder = new TreeBuilder(); 28 | $rootNode = $treeBuilder->root('leaphly_cart'); 29 | 30 | $rootNode 31 | ->children() 32 | ->scalarNode('cart_class')->isRequired()->cannotBeEmpty()->end() 33 | ->scalarNode('cart_manager')->defaultValue('leaphly_cart.cart_manager.default')->end() 34 | ->scalarNode('model_manager_name')->defaultNull()->end() 35 | ->scalarNode('product_family_provider')->isRequired()->cannotBeEmpty()->end() 36 | ->booleanNode('use_price_listener')->defaultTrue()->end() 37 | ->scalarNode('cart_transition')->defaultValue('leaphly_cart.cart.finite.transition')->end() 38 | ->end(); 39 | 40 | $this->addDbDriver($rootNode); 41 | $this->addRoles($rootNode); 42 | 43 | return $treeBuilder; 44 | } 45 | 46 | private function addDbDriver(ArrayNodeDefinition $node) 47 | { 48 | $node 49 | ->children() 50 | ->scalarNode('db_driver') 51 | ->validate() 52 | ->ifNotInArray(self::$supportedDrivers) 53 | ->thenInvalid('The driver %s is not supported. Please choose one of '.json_encode(self::$supportedDrivers)) 54 | ->end() 55 | ->cannotBeOverwritten() 56 | ->isRequired() 57 | ->cannotBeEmpty() 58 | ->end() 59 | ->end(); 60 | } 61 | 62 | private function addRoles(ArrayNodeDefinition $node) 63 | { 64 | $node 65 | ->addDefaultsIfNotSet() 66 | ->children() 67 | ->arrayNode('roles') 68 | ->useAttributeAsKey('name') 69 | ->prototype('array') 70 | ->children() 71 | ->booleanNode('is_default')->defaultFalse()->end() 72 | ->arrayNode('handler') 73 | ->canBeUnset() 74 | ->children() 75 | ->scalarNode('cart')->cannotBeEmpty()->end() 76 | ->scalarNode('item')->cannotBeEmpty()->end() 77 | ->end() 78 | ->end() 79 | ->arrayNode('controller') 80 | ->children() 81 | ->scalarNode('cart')->cannotBeEmpty()->end() 82 | ->scalarNode('item')->cannotBeEmpty()->end() 83 | ->end() 84 | ->end() 85 | ->scalarNode('form')->end() 86 | ->scalarNode('strategy')->end() 87 | ->scalarNode('fallback_strategy')->end() 88 | ->end() 89 | ->validate() 90 | ->ifTrue(function ($v) {return !isset($v['form']) && !isset($v['handler']['cart']);}) 91 | ->thenInvalid('You need to specify or the form or the cart handler.') 92 | ->end() 93 | ->end() 94 | ->isRequired() 95 | ->cannotBeEmpty() 96 | ->validate() 97 | ->ifTrue(function ($v) {return (count($v) < 1);}) 98 | ->thenInvalid('You need to specify at least one role.') 99 | ->end() 100 | ->validate() 101 | ->ifTrue(function ($roles) { 102 | $counter = 0; 103 | foreach ($roles as $role) { 104 | $counter += (isset($role['is_default']) && $role['is_default'])? 1 : 0; 105 | } 106 | 107 | return ($counter > 1); 108 | }) 109 | ->thenInvalid('Multiple `is_default` defined.') 110 | ->end() 111 | ->end() 112 | ; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Controller/CartItemsController.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class CartItemsController extends BaseController 17 | { 18 | protected $itemHandler; 19 | 20 | public function setItemHandler(CartItemHandlerInterface $itemHandler) 21 | { 22 | $this->itemHandler = $itemHandler; 23 | } 24 | 25 | /** 26 | * 27 | * @ApiDoc( 28 | * resource=true 29 | * ) 30 | * 31 | * @api 32 | * 33 | * @param mixed $cart_id 34 | * 35 | * @return Response 36 | */ 37 | public function postItemAction($cart_id) 38 | { 39 | $cart = $this->fetchCartOr404($cart_id); 40 | $headers = array('Location' => 41 | $this->container->get('router')->generate( 42 | 'api_1_get_cart', array('cart_id' => $cart->getId(), '_format' => $this->container->get('request')->get('_format') ), 43 | true) 44 | ); 45 | 46 | try { 47 | $this->itemHandler 48 | ->postItem( 49 | $cart, 50 | $this->container->get('request')->request->all() 51 | ); 52 | 53 | $view = $this->view($cart, 201, $headers) 54 | ->setTemplate("LeaphlyCartBundle:Carts:getCart.html.twig") 55 | ->setTemplateVar('cart'); 56 | 57 | } catch (InvalidFormException $exception) { 58 | return $this->view(array('errors' => $exception->getForm()), 422, $headers); 59 | 60 | } catch (BadRequestHttpException $ex) { 61 | 62 | $view = $this->view($cart, $ex->getCode()) 63 | ->setTemplate("LeaphlyCartBundle:Carts:getCart.html.twig") 64 | ->setTemplateVar('cart'); 65 | 66 | } 67 | 68 | return $view; 69 | } 70 | 71 | /** 72 | * 73 | * @ApiDoc( 74 | * resource=true 75 | * ) 76 | * 77 | * @api 78 | * 79 | * @param mixed $cart_id 80 | * @param mixed $item_id 81 | * 82 | * @return Response 83 | */ 84 | public function patchItemAction($cart_id, $item_id) 85 | { 86 | $cart = $this->fetchCartOr404($cart_id); 87 | $item = $cart->getItemById($item_id); 88 | 89 | $headers = array( 90 | 'Location' => $this->container->get('router')->generate( 91 | 'api_1_get_cart', array( 92 | 'cart_id' => $cart->getId(), 93 | '_format' => $this->container->get('request')->get('_format') 94 | ), true 95 | ) 96 | ); 97 | 98 | try { 99 | $cart = $this->itemHandler 100 | ->patchItem( 101 | $cart, 102 | $item, 103 | $this->container->get('request')->request->all() 104 | ); 105 | 106 | $view = $this->view($cart, 200, $headers) 107 | ->setTemplate("LeaphlyCartBundle:Carts:getCart.html.twig") 108 | ->setTemplateVar('cart'); 109 | 110 | } catch (InvalidFormException $exception) { 111 | return $this->view(array('errors' => $exception->getForm()), 422); 112 | 113 | } catch (BadRequestHttpException $ex) { 114 | 115 | $view = $this->view($cart, $ex->getCode()) 116 | ->setTemplate("LeaphlyCartBundle:Carts:getCart.html.twig") 117 | ->setTemplateVar('cart'); 118 | 119 | } 120 | 121 | return $view; 122 | } 123 | 124 | /** 125 | * Delete Item. 126 | * 127 | * @ApiDoc( 128 | * resource=true 129 | * ) 130 | * 131 | * @api 132 | * 133 | * @param mixed $cart_id 134 | * @param mixed $item_id 135 | * 136 | * @return Response 137 | * 138 | * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException 139 | * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException 140 | */ 141 | public function deleteItemAction($cart_id, $item_id) 142 | { 143 | $cart = $this->fetchCartOr404($cart_id); 144 | $item = $cart->getItemById($item_id); 145 | 146 | if (!$item) { 147 | throw new NotFoundHttpException(); 148 | } 149 | 150 | try { 151 | $this->itemHandler->deleteItem($cart, $item); 152 | 153 | $view = $this->view($cart, 200) 154 | ->setTemplate("LeaphlyCartBundle:Carts:getCart.html.twig") 155 | ->setTemplateVar('cart'); 156 | 157 | } catch (BadRequestHttpException $ex) { 158 | 159 | $view = $this->view($cart, $ex->getCode()) 160 | ->setTemplate("LeaphlyCartBundle:Carts:getCart.html.twig") 161 | ->setTemplateVar('cart'); 162 | } 163 | 164 | return $view; 165 | } // "delete_cart_item" [DELETE] /carts/{cart_id}/items/{item_id} 166 | 167 | /** 168 | * Delete all Items. 169 | * 170 | * @ApiDoc( 171 | * resource=true 172 | * ) 173 | * 174 | * @api 175 | * 176 | * @param mixed $cart_id 177 | * 178 | * @return Response 179 | * 180 | * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException 181 | * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException 182 | */ 183 | public function deleteItemsAction($cart_id) 184 | { 185 | $cart = $this->fetchCartOr404($cart_id); 186 | 187 | try { 188 | $this->itemHandler->deleteAllItems($cart); 189 | 190 | $view = $this->view($cart, 200) 191 | ->setTemplate("LeaphlyCartBundle:Carts:getCart.html.twig") 192 | ->setTemplateVar('cart'); 193 | } catch (BadRequestHttpException $ex) { 194 | 195 | $view = $this->view($cart, $ex->getCode()) 196 | ->setTemplate("LeaphlyCartBundle:Carts:getCart.html.twig") 197 | ->setTemplateVar('cart'); 198 | } 199 | 200 | return $view; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /Controller/CartsController.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class CartsController extends BaseController 21 | { 22 | /** 23 | * Get a single cart. 24 | * 25 | * @ApiDoc( 26 | * resource = true, 27 | * output = "Acme\Cart\Model\CartInterface", 28 | * statusCodes = { 29 | * 200 = "Returned when successful", 30 | * 404 = "Returned when the cart is not found" 31 | * } 32 | * ) 33 | * @api 34 | * @Annotations\View(templateVar="cart") 35 | * 36 | * @param Request $request the request object 37 | * @param int $cart_id the cart id 38 | * 39 | * @return array 40 | * 41 | * @throws NotFoundHttpException when cart not exist 42 | */ 43 | public function getCartAction(Request $request, $cart_id) 44 | { 45 | $cart = $this->fetchCartOr404($cart_id); 46 | 47 | return $cart; 48 | } 49 | 50 | /** 51 | * Removes a cart. 52 | * 53 | * @ApiDoc( 54 | * resource = true, 55 | * statusCodes={ 56 | * 204="Returned when successful", 57 | * 400="Returned when request is a bad request", 58 | * 404="Returned when the cart is not found" 59 | * } 60 | * ) 61 | * 62 | * @api 63 | * @Annotations\View(statusCode = Codes::HTTP_NO_CONTENT) 64 | * 65 | * @param Request $request the request object 66 | * @param int $cart_id the cart id 67 | * 68 | * @return array|View 69 | * 70 | * @throws NotFoundHttpException when cart not exist 71 | */ 72 | public function deleteCartAction(Request $request, $cart_id) 73 | { 74 | $cart = $this->fetchCartOr404($cart_id); 75 | 76 | try { 77 | $this->cartHandler->deleteCart($cart); 78 | } catch (BadRequestHttpException $ex) { 79 | return $this->view($cart, $ex->getCode()) 80 | ->setTemplate("LeaphlyCartBundle:Carts:getCart.html.twig") 81 | ->setTemplateVar('cart'); 82 | } 83 | 84 | return array(); 85 | } 86 | 87 | /** 88 | * Create a new cart from the submitted data. 89 | * 90 | * @ApiDoc( 91 | * resource = true, 92 | * input = "Leaphly\Cart\Form\Type\CartFormType", 93 | * statusCodes = { 94 | * 201 = "Returned when successful created", 95 | * 400 = "Returned when the form has errors" 96 | * } 97 | * ) 98 | * 99 | * @Annotations\View( 100 | * template = "AcmeCartBundle:Cart:newCart.html.twig", 101 | * statusCode = Codes::HTTP_BAD_REQUEST 102 | * ) 103 | * 104 | * @param Request $request the request object 105 | * 106 | * @return FormTypeInterface[]|RouteRedirectView 107 | */ 108 | public function postCartAction(Request $request) 109 | { 110 | try { 111 | $cart = $this->cartHandler->postCart($request->request->all()); 112 | } catch (InvalidFormException $exception) { 113 | return array('form' => $exception->getForm()); 114 | } 115 | 116 | $routeOptions = array( 117 | 'cart_id' => $cart->getId(), 118 | '_format' => $request->get('_format') 119 | ); 120 | 121 | return $this->routeRedirectView('api_1_get_cart', $routeOptions, Codes::HTTP_CREATED); 122 | } 123 | 124 | /** 125 | * Update existing cart from the submitted data or create a new cart at a specific location. 126 | * 127 | * @ApiDoc( 128 | * resource = true, 129 | * input = "Leaphly\Cart\Form\Type\CartFormType", 130 | * statusCodes = { 131 | * 201 = "Returned when creates a new cart", 132 | * 204 = "Returned when successful", 133 | * 400 = "Returned when the form has errors", 134 | * } 135 | * ) 136 | * 137 | * @Annotations\View( 138 | * template="AcmeDemoBundle:Cart:editCart.html.twig", 139 | * statusCode = Codes::HTTP_BAD_REQUEST 140 | * ) 141 | * 142 | * @param Request $request the request object 143 | * @param int $cart_id the cart id 144 | * 145 | * @return FormTypeInterface[]|RouteRedirectView 146 | */ 147 | public function putCartAction(Request $request, $cart_id) 148 | { 149 | try { 150 | if (!($cart = $this->cartHandler->getCart($cart_id))) { 151 | $cart = $this->cartHandler->postCart($request->request->all()); 152 | $statusCode = Codes::HTTP_CREATED; 153 | } else { 154 | $cart = $this->cartHandler->putCart($cart, $request->request->all()); 155 | $statusCode = Codes::HTTP_NO_CONTENT; 156 | } 157 | } catch (InvalidFormException $exception) { 158 | return array('form' => $exception->getForm()); 159 | } 160 | 161 | $routeOptions = array( 162 | 'cart_id' => $cart->getId(), 163 | '_format' => $request->get('_format') 164 | ); 165 | 166 | return $this->routeRedirectView('api_1_get_cart', $routeOptions, $statusCode); 167 | } 168 | 169 | /** 170 | * Partially Update existing cart from the submitted data or create a new cart at a specific location. 171 | * 172 | * @ApiDoc( 173 | * resource = true, 174 | * input = "Leaphly\Cart\Form\Type\CartFormType", 175 | * statusCodes = { 176 | * 204 = "Returned when successful", 177 | * 400 = "Returned when the form has errors", 178 | * } 179 | * ) 180 | * 181 | * @Annotations\View( 182 | * template="AcmeDemoBundle:Cart:editCart.html.twig", 183 | * statusCode = Codes::HTTP_BAD_REQUEST 184 | * ) 185 | * 186 | * @param Request $request the request object 187 | * @param int $cart_id the cart id 188 | * 189 | * @return FormTypeInterface[]|RouteRedirectView 190 | */ 191 | public function patchCartAction(Request $request, $cart_id) 192 | { 193 | $cart = $this->fetchCartOr404($cart_id); 194 | 195 | try { 196 | $this->cartHandler->patchCart($cart, $request->request->all()); 197 | } catch (InvalidFormException $exception) { 198 | return array('form' => $exception->getForm()); 199 | } 200 | 201 | $routeOptions = array( 202 | 'cart_id' => $cart->getId(), 203 | '_format' => $request->get('_format') 204 | ); 205 | 206 | return $this->routeRedirectView('api_1_get_cart', $routeOptions, Codes::HTTP_NO_CONTENT); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /DependencyInjection/LeaphlyCartExtension.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class LeaphlyCartExtension extends Extension 18 | { 19 | public function load(array $configs, ContainerBuilder $container) 20 | { 21 | $processor = new Processor(); 22 | $configuration = new Configuration(); 23 | $config = $processor->processConfiguration($configuration, $configs); 24 | 25 | $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); 26 | 27 | $this->loadDbDriver($config, $container, $loader); 28 | $this->loadServices(array('calculator', 'listener', 'event', 'transition', 'parameter'), $loader); 29 | 30 | $container->setAlias('leaphly_cart.cart.transition', $config['cart_transition']); 31 | $container->setAlias('leaphly_cart.cart_manager', $config['cart_manager']); 32 | $container->setAlias('leaphly_cart.product_family_provider', $config['product_family_provider']); 33 | 34 | $this->remapParametersNamespaces($config, $container, array( 35 | '' => array( 36 | 'model_manager_name' => 'leaphly_cart.model_manager_name', 37 | 'cart_class' => 'leaphly_cart.model.cart.class' 38 | ) 39 | )); 40 | 41 | $this->registerPriceListener($config, $container); 42 | $container->setParameter('leaphly_cart.cart.form.name', 'cart'); 43 | $this->loadFormType($container, $loader, $config); 44 | $this->loadRoles($config, $container); 45 | 46 | $this->defineGodfatherConfiguration($config, $container); 47 | } 48 | 49 | private function registerPriceListener($config, ContainerBuilder $container) 50 | { 51 | if ($config['use_price_listener']) { 52 | $container->getDefinition('leaphly_cart.listener.price_calculator')->addTag( 53 | 'kernel.event_listener', 54 | array('method' => 'calculatePrice', 'event'=>\Leaphly\Cart\LeaphlyCartEvents::CART_CREATE_SUCCESS) 55 | ); 56 | $container->getDefinition('leaphly_cart.listener.price_calculator')->addTag( 57 | 'kernel.event_listener', 58 | array('method' => 'calculatePrice', 'event'=>\Leaphly\Cart\LeaphlyCartEvents::CART_EDIT_SUCCESS) 59 | ); 60 | $container->getDefinition('leaphly_cart.listener.price_calculator')->addTag( 61 | 'kernel.event_listener', 62 | array('method' => 'calculatePrice', 'event'=>\Leaphly\Cart\LeaphlyCartEvents::ITEM_CREATE_SUCCESS) 63 | ); 64 | $container->getDefinition('leaphly_cart.listener.price_calculator')->addTag( 65 | 'kernel.event_listener', 66 | array('method' => 'calculatePrice', 'event'=>\Leaphly\Cart\LeaphlyCartEvents::ITEM_DELETE_COMPLETED) 67 | ); 68 | } 69 | } 70 | 71 | private function loadRoles($config, ContainerBuilder $container) 72 | { 73 | $cartControllerClass = $container->getParameter('leaphly_cart.cart.controller.class'); 74 | $cartHandlerClass = $container->getParameter('leaphly_cart.cart.handler.class'); 75 | $itemControllerClass = $container->getParameter('leaphly_cart.item.controller.class'); 76 | $itemHandlerClass = $container->getParameter('leaphly_cart.item.handler.class'); 77 | $cartFormFactoryHandlerClass = $container->getParameter('leaphly_cart.cart.form.factory.class'); 78 | $defaultCartHandler = 'leaphly_cart.cart.handler'; 79 | $defaultCartItemHandler = 'leaphly_cart.cart_item.handler'; 80 | 81 | if (isset($config['roles'])) { 82 | foreach ($config['roles'] as $roleName => $role) { 83 | 84 | $cartHandlerId = sprintf('leaphly_cart.cart.%s.handler', $roleName); 85 | $controllerId = sprintf('leaphly_cart.cart.%s.controller', $roleName); 86 | $formFactoryName = sprintf('leaphly_cart.cart.%s.form.factory', $roleName); 87 | $cartItemHandlerId = sprintf('leaphly_cart.cart_item.%s.handler', $roleName); 88 | $controllerCartItemId =sprintf('leaphly_cart.cart_item.%s.controller', $roleName); 89 | 90 | // if the handler is not defined explicitally, register the cart.handler 91 | if (isset($role['form']) && !isset($role['handler']['cart'])) { 92 | $this->registerFormFactory($container, $formFactoryName, $cartFormFactoryHandlerClass, '%leaphly_cart.cart.form.name%', $role['form']); 93 | $this->registerCartHandler($container, $cartHandlerId, $cartHandlerClass, $formFactoryName); 94 | } elseif (isset($role['handler']['cart'])) { 95 | $container->setAlias($cartHandlerId, $role['handler']['cart']); 96 | } 97 | 98 | // use the cart handler in the controller 99 | if (!isset($role['controller']['cart'])) { 100 | $this->registerController( 101 | $container, 102 | $controllerId, 103 | $cartControllerClass, 104 | array( 105 | array('setContainer', array(new Reference('service_container'))), 106 | array('setCartHandler', array(new Reference($cartHandlerId))) 107 | ) 108 | ); 109 | } else { 110 | $container->setAlias($controllerId, $role['controller']['cart']); 111 | } 112 | 113 | $strategy = isset($role['strategy']) ? $role['strategy'] : sprintf('godfather.%s', $roleName); 114 | 115 | if (isset($role['form']) && !isset($role['handler']['item'])) { 116 | $this->registerCartItemHandler($container, $cartItemHandlerId, $itemHandlerClass, $strategy); 117 | } elseif (isset($role['handler']['item'])) { 118 | $container->setAlias($cartItemHandlerId, $role['handler']['item']); 119 | } 120 | 121 | if (!isset($role['controller']['item'])) { 122 | $this->registerController( 123 | $container, 124 | $controllerCartItemId, 125 | $itemControllerClass, 126 | array( 127 | array('setContainer', array(new Reference('service_container'))), 128 | array('setCartHandler', array(new Reference($cartHandlerId))), 129 | array('setItemHandler', array(new Reference($cartItemHandlerId))) 130 | ) 131 | ); 132 | } else { 133 | $container->setAlias($controllerCartItemId, $role['controller']['item']); 134 | } 135 | 136 | // setting the default cart hanlder 137 | if (!$container->hasAlias($defaultCartHandler) || $role['is_default']) { 138 | $container->setAlias($defaultCartHandler, $cartHandlerId); 139 | } 140 | // setting the default cart hanlder 141 | if (!$container->hasAlias($defaultCartItemHandler) || $role['is_default']) { 142 | $container->setAlias($defaultCartItemHandler, $cartItemHandlerId); 143 | } 144 | 145 | } 146 | } 147 | } 148 | 149 | private function loadDbDriver($config, ContainerBuilder $container, XmlFileLoader $loader) 150 | { 151 | $loader->load(sprintf('%s.xml', $config['db_driver'])); 152 | $container->setParameter($this->getAlias() . '.backend_type_' . $config['db_driver'], true); 153 | } 154 | 155 | private function loadServices(array $services, XmlFileLoader $loader) 156 | { 157 | foreach ($services as $basename) { 158 | $loader->load(sprintf('%s.xml', $basename)); 159 | } 160 | } 161 | 162 | private function registerController(ContainerBuilder $container, $id, $class, $calls = array()) 163 | { 164 | $controller = new Definition($class); 165 | $controller->setMethodCalls($calls); 166 | $container->setDefinition($id, $controller); 167 | } 168 | 169 | private function registerService(ContainerBuilder $container, $id, $class, $params = array()) 170 | { 171 | $arguments = array_map(function ($arg) use ($container) { 172 | if ($arg[0] == '%' && $arg[strlen($arg) - 1] == '%') { 173 | return $container->getParameter(str_replace('%', '', $arg)); 174 | } else { 175 | return new Reference($arg); 176 | } 177 | }, $params); 178 | $service = new Definition($class, $arguments); 179 | $container->setDefinition($id, $service); 180 | } 181 | 182 | private function registerFormFactory(ContainerBuilder $container, $formNameId, $cartFormFactoryHandlerClass, $formName, $formType) 183 | { 184 | $this->registerService( 185 | $container, 186 | $formNameId, 187 | $cartFormFactoryHandlerClass, 188 | array( 189 | 'form.factory', 190 | $formName, 191 | $formType 192 | ) 193 | ); 194 | } 195 | 196 | private function registerCartHandler(ContainerBuilder $container, $nameId, $cartHandlerClass, $formFactoryName) 197 | { 198 | $this->registerService( 199 | $container, 200 | $nameId, 201 | $cartHandlerClass, 202 | array( 203 | 'leaphly_cart.cart_manager', 204 | $formFactoryName, 205 | 'leaphly_cart.cart.transition', 206 | 'event_dispatcher', 207 | 'leaphly_cart.cart_event_factory' 208 | ) 209 | ); 210 | } 211 | 212 | private function registerCartItemHandler(ContainerBuilder $container, $nameId, $cartItemHandlerClass, $strategy) 213 | { 214 | $this->registerService( 215 | $container, 216 | $nameId, 217 | $cartItemHandlerClass, 218 | array( 219 | 'leaphly_cart.cart_manager', 220 | 'leaphly_cart.product_family_provider', 221 | $strategy, 222 | 'leaphly_cart.cart.transition', 223 | 'event_dispatcher', 224 | 'leaphly_cart.cart_item.event_factory' 225 | ) 226 | ); 227 | } 228 | 229 | private function defineGodfatherConfiguration($config, ContainerBuilder $container) 230 | { 231 | $godfatherConfiguration = array(); 232 | 233 | foreach ($config['roles'] as $roleName => $role) { 234 | $roleStrategy = array( 235 | 'contexts' => array( 236 | 'item_handler' => array( 237 | ), 238 | ) 239 | ); 240 | 241 | if (isset($role['fallback_strategy'])) { 242 | $roleStrategy['contexts']['handler']['fallback'] = $role['fallback_strategy']; 243 | } 244 | $godfatherConfiguration['godfather'][$roleName] = $roleStrategy; 245 | } 246 | 247 | $strategy = new GodfatherExtension(); 248 | $strategy->load($godfatherConfiguration, $container); 249 | } 250 | 251 | private function loadFormType(ContainerBuilder $container, XmlFileLoader $loader, $config) 252 | { 253 | $loader->load('form.xml'); 254 | } 255 | 256 | protected function remapParameters(array $config, ContainerBuilder $container, array $map) 257 | { 258 | foreach ($map as $name => $paramName) { 259 | if (array_key_exists($name, $config)) { 260 | $container->setParameter($paramName, $config[$name]); 261 | } 262 | } 263 | } 264 | 265 | protected function remapParametersNamespaces(array $config, ContainerBuilder $container, array $namespaces) 266 | { 267 | foreach ($namespaces as $ns => $map) { 268 | if ($ns) { 269 | if (!array_key_exists($ns, $config)) { 270 | continue; 271 | } 272 | $namespaceConfig = $config[$ns]; 273 | } else { 274 | $namespaceConfig = $config; 275 | } 276 | if (is_array($map)) { 277 | $this->remapParameters($namespaceConfig, $container, $map); 278 | } else { 279 | foreach ($namespaceConfig as $name => $value) { 280 | $container->setParameter(sprintf($map, $name), $value); 281 | } 282 | } 283 | } 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/LeaphlyCartExtensionTest.php: -------------------------------------------------------------------------------- 1 | 12 | * @package Leaphly\CartBundle\Tests\DependencyInjection 13 | */ 14 | class LeaphlyCartExtensionTest extends \PHPUnit_Framework_TestCase 15 | { 16 | /** @var ContainerBuilder */ 17 | protected $configuration; 18 | 19 | /** 20 | * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException 21 | */ 22 | public function testCartLoadThrowsExceptionUnlessDatabaseDriverSet() 23 | { 24 | $loader = new LeaphlyCartExtension(); 25 | $config = $this->getFullConfigForOneRole(); 26 | unset($config['db_driver']); 27 | $loader->load(array($config), new ContainerBuilder()); 28 | } 29 | 30 | /** 31 | * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException 32 | */ 33 | public function testCartLoadThrowsExceptionUnlessDatabaseDriverIsValid() 34 | { 35 | $loader = new LeaphlyCartExtension(); 36 | $config = $this->getFullConfigForOneRole(); 37 | $config['db_driver'] = 'foo'; 38 | $loader->load(array($config), new ContainerBuilder()); 39 | } 40 | 41 | /** 42 | * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException 43 | */ 44 | public function testCartLoadThrowsExceptionUnlessCartModelClassSet() 45 | { 46 | $loader = new LeaphlyCartExtension(); 47 | $config = $this->getFullConfigForOneRole(); 48 | unset($config['cart_class']); 49 | $loader->load(array($config), new ContainerBuilder()); 50 | } 51 | 52 | public function testCartLoadModelClass() 53 | { 54 | $this->createFullConfiguration(); 55 | $this->assertParameter('Acme\MyBundle\Entity\Cart', 'leaphly_cart.model.cart.class'); 56 | } 57 | 58 | public function testDefaultCartTransitionWithAlias() 59 | { 60 | $this->createFullConfiguration(); 61 | $this->assertAlias('leaphly_cart.cart.finite.transition', 'leaphly_cart.cart.transition'); 62 | } 63 | 64 | public function testCustomCartTransition() 65 | { 66 | $config = $this->getOnlyFormConfig(); 67 | $config['cart_transition'] = 'my.custom'; 68 | $this->createConfiguration($config); 69 | $this->assertNotAlias('leaphly_cart.cart.finite.transition', 'leaphly_cart.cart.transition'); 70 | $this->assertHasDefinition('leaphly_cart.cart.transition'); 71 | } 72 | 73 | public function testCartLoadManagerClass() 74 | { 75 | $this->createFullConfiguration(); 76 | $this->assertAlias('acme_my.product_family_provider', 'leaphly_cart.product_family_provider'); 77 | } 78 | 79 | public function testCartHandlerRegistration() 80 | { 81 | $this->createConfiguration($this->getOnlyFormConfig()); 82 | 83 | $this->assertHasDefinition('leaphly_cart.cart.full.handler'); 84 | $this->assertHasDefinition('leaphly_cart.cart.limited.handler'); 85 | } 86 | 87 | public function testControllersRegistration() 88 | { 89 | $this->createConfiguration($this->getOnlyFormConfig()); 90 | $this->assertHasDefinition('leaphly_cart.cart.full.controller'); 91 | $this->assertHasDefinition('leaphly_cart.cart.limited.controller'); 92 | } 93 | 94 | public function testGodFatherAutoConfiguration() 95 | { 96 | $this->createConfiguration($this->getFullConfig()); 97 | 98 | $this->assertHasDefinition('godfather.full'); 99 | $this->assertHasDefinition('godfather.limited'); 100 | } 101 | 102 | /** 103 | * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException 104 | */ 105 | public function testExpectedExceptionWhenMultipleDefaultRole() 106 | { 107 | $roleName = 'full'; 108 | $config = $this->getFullConfig(); 109 | $config['roles']['full']['is_default'] = true; 110 | $config['roles']['limited']['is_default'] = true; 111 | $this->createConfiguration($config); 112 | } 113 | 114 | public function testSetDefaultRole() 115 | { 116 | $cartHandlerId = 'leaphly_cart.cart.handler'; 117 | 118 | $config = $this->getFullConfig(); 119 | $config['roles']['limited']['is_default'] = true; 120 | $this->createConfiguration($config); 121 | 122 | $this->assertAlias('leaphly_cart.cart.limited.handler', $cartHandlerId); 123 | } 124 | 125 | public function testIsDefaultRole() 126 | { 127 | $cartHandlerId = 'leaphly_cart.cart.handler'; 128 | 129 | $config = $this->getFullConfig(); 130 | $config['roles']['full']['is_default'] = true; 131 | $config['roles']['limited']['is_default'] = false; 132 | $this->createConfiguration($config); 133 | 134 | $this->assertAlias('leaphly_cart.cart.full.handler', $cartHandlerId); 135 | } 136 | 137 | public function testAllExplicitRoles() 138 | { 139 | $roleName = 'full'; 140 | $cartHandlerId = sprintf('leaphly_cart.cart.%s.handler', $roleName); 141 | $controllerId = sprintf('leaphly_cart.cart.%s.controller', $roleName); 142 | $cartItemHandlerId = sprintf('leaphly_cart.cart_item.%s.handler', $roleName); 143 | $controllerCartItemId =sprintf('leaphly_cart.cart_item.%s.controller', $roleName); 144 | 145 | $this->createConfiguration($this->getFullConfigForOneRole()); 146 | $this->assertAlias('full.controller.cart', $controllerId); 147 | $this->assertAlias('full.controller.item', $controllerCartItemId); 148 | $this->assertAlias('full.handler.cart', $cartHandlerId); 149 | $this->assertAlias('full.handler.item', $cartItemHandlerId); 150 | } 151 | 152 | public function testControllerShouldBeRegisteredDefiningFormAndHandlersExplicitly() 153 | { 154 | $roleName = 'full'; 155 | $cartHandlerId = sprintf('leaphly_cart.cart.%s.handler', $roleName); 156 | $controllerId = sprintf('leaphly_cart.cart.%s.controller', $roleName); 157 | $cartItemHandlerId = sprintf('leaphly_cart.cart_item.%s.handler', $roleName); 158 | $controllerCartItemId =sprintf('leaphly_cart.cart_item.%s.controller', $roleName); 159 | 160 | $config = $this->getFullConfigForOneRole(); 161 | unset($config['roles']['full']['controller']); 162 | $this->createConfiguration($config); 163 | $this->assertHasDefinition($controllerId); 164 | $this->assertHasDefinition($controllerCartItemId); 165 | $this->assertAlias('full.handler.cart', $cartHandlerId); 166 | $this->assertAlias('full.handler.item', $cartItemHandlerId); 167 | } 168 | 169 | public function testHandlersAndControllerShouldBeRegisteredDefiningFormExplicitly() 170 | { 171 | $roleName = 'full'; 172 | $cartHandlerId = sprintf('leaphly_cart.cart.%s.handler', $roleName); 173 | $controllerId = sprintf('leaphly_cart.cart.%s.controller', $roleName); 174 | $formFactoryName = sprintf('leaphly_cart.cart.%s.form.factory', $roleName); 175 | $cartItemHandlerId = sprintf('leaphly_cart.cart_item.%s.handler', $roleName); 176 | $controllerCartItemId =sprintf('leaphly_cart.cart_item.%s.controller', $roleName); 177 | 178 | $config = $this->getFullConfigForOneRole(); 179 | unset($config['roles']['full']['handler']); 180 | unset($config['roles']['full']['controller']); 181 | $this->createConfiguration($config); 182 | $this->assertHasDefinition($controllerId); 183 | $this->assertHasDefinition($controllerCartItemId); 184 | $this->assertHasDefinition($cartHandlerId); 185 | $this->assertHasDefinition($cartItemHandlerId); 186 | } 187 | 188 | public function testControllerShouldBeRegisteredDefiningHandlersExplicitly() 189 | { 190 | $roleName = 'full'; 191 | $cartHandlerId = sprintf('leaphly_cart.cart.%s.handler', $roleName); 192 | $controllerId = sprintf('leaphly_cart.cart.%s.controller', $roleName); 193 | $cartItemHandlerId = sprintf('leaphly_cart.cart_item.%s.handler', $roleName); 194 | $controllerCartItemId =sprintf('leaphly_cart.cart_item.%s.controller', $roleName); 195 | 196 | $config = $this->getFullConfigForOneRole(); 197 | unset($config['roles']['form']); 198 | unset($config['roles']['full']['controller']); 199 | $this->createConfiguration($config); 200 | $this->assertHasDefinition($controllerId); 201 | $this->assertHasDefinition($controllerCartItemId); 202 | $this->assertHasDefinition($cartHandlerId); 203 | $this->assertHasDefinition($cartItemHandlerId); 204 | } 205 | 206 | /** 207 | * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException 208 | */ 209 | public function testWithRolesExpectedException() 210 | { 211 | $config = $this->getBaseConfig(); 212 | $this->createConfiguration($config); 213 | } 214 | 215 | /** 216 | * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException 217 | */ 218 | public function testWithoutRolesExpectedException() 219 | { 220 | $config = $this->getBaseConfig(); 221 | unset($config['roles']); 222 | $this->createConfiguration($config); 223 | } 224 | 225 | /** 226 | * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException 227 | */ 228 | public function testNotDefiningFormAndHandlersExceptionShouldBeExpected() 229 | { 230 | 231 | $config = $this->getFullConfigForOneRole(); 232 | unset($config['roles']['full']['form']); 233 | unset($config['roles']['full']['handler']); 234 | $this->createConfiguration($config); 235 | } 236 | 237 | protected function createFullConfiguration() 238 | { 239 | $this->configuration = new ContainerBuilder(); 240 | $loader = new LeaphlyCartExtension(); 241 | $config = $this->getFullConfig(); 242 | $loader->load(array($config), $this->configuration); 243 | $this->assertTrue($this->configuration instanceof ContainerBuilder); 244 | } 245 | 246 | protected function createConfiguration($config) 247 | { 248 | $this->configuration = new ContainerBuilder(); 249 | $loader = new LeaphlyCartExtension(); 250 | $loader->load(array($config), $this->configuration); 251 | $this->assertTrue($this->configuration instanceof ContainerBuilder); 252 | } 253 | 254 | /** 255 | * getBaseConfig 256 | * 257 | * @return array 258 | */ 259 | protected function getBaseConfig() 260 | { 261 | return array( 262 | 'db_driver' => 'mongodb', 263 | 'cart_class' => 'Acme\MyBundle\Document\Cart', 264 | 'product_family_provider' => 'acme_my.product_family_provider', 265 | 'roles' => array() 266 | ); 267 | } 268 | protected function getFullConfig() 269 | { 270 | $yaml = <<parse($yaml); 294 | } 295 | 296 | protected function getOnlyFormConfig() 297 | { 298 | $yaml = <<parse($yaml); 312 | } 313 | 314 | 315 | protected function getFullConfigForOneRole() 316 | { 317 | $yaml = <<parse($yaml); 334 | } 335 | 336 | /** 337 | * @param string $value 338 | * @param string $key 339 | */ 340 | private function assertAlias($value, $key) 341 | { 342 | $this->assertEquals($value, (string) $this->configuration->getAlias($key), sprintf('%s alias is correct', $key)); 343 | } 344 | 345 | /** 346 | * @param string $value 347 | * @param string $key 348 | */ 349 | private function assertNotAlias($value, $key) 350 | { 351 | $this->assertNotEquals($value, (string) $this->configuration->getAlias($key), sprintf('%s alias is correct', $key)); 352 | } 353 | 354 | /** 355 | * @param mixed $value 356 | * @param string $key 357 | */ 358 | private function assertParameter($value, $key) 359 | { 360 | $this->assertEquals($value, $this->configuration->getParameter($key), sprintf('%s parameter is correct', $key)); 361 | } 362 | 363 | /** 364 | * @param string $id 365 | */ 366 | private function assertHasDefinition($id) 367 | { 368 | $this->assertTrue(($this->configuration->hasDefinition($id) ?: $this->configuration->hasAlias($id))); 369 | } 370 | 371 | /** 372 | * @param string $id 373 | */ 374 | private function assertNotHasDefinition($id) 375 | { 376 | $this->assertFalse(($this->configuration->hasDefinition($id) ?: $this->configuration->hasAlias($id))); 377 | } 378 | 379 | protected function tearDown() 380 | { 381 | unset($this->configuration); 382 | } 383 | } 384 | --------------------------------------------------------------------------------