meet elcodi, the flexible e-commerce components built on symfony2
TRANSCRIPT
Programming made fun
ELCODI
github.com/mmoreram
github.com/alch
github.com/elcodi-bot
@zimage
@elcodi_dev
@mmoreramMarc Morera
Aldo Chiecchia
Elcodi Bot
it began as an internal project@befactory
LET’s TALK ABOUT…
Hello World!
Simple doctrine mapping
The Cart system
Annotations
did you just say Annotations?
DON’T PANIC
1.HELLO WORLDWhat is my purpose in life?
SYMFONY2 ECOMMERCE APPS
S.O.L.I.D. Reusable
Testable Community
philosophy
Elcodi was born to be a Symfony project
Symfony components Symfony Framework
we love all of symfony…so we are putting our efforts in both
coupled to the framework?OMFG!
Why not get the best of both worlds?
Symfony components for advanced devs
Symfony framework for the pragmatic devs
Everybody’s happy
Core stuff, only symfony components
Web app, frontend
Web app, backoffice
The playground!
with symfony-standard
"require": { "elcodi/elcodi": "~0.4", "elcodi/bamboo-fixtures": “~0.1”, “doctrine/doctrine-fixtures-bundle": “v2.2.0"}
composer.json
new Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle(),new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),new Knp\Bundle\GaufretteBundle\KnpGaufretteBundle(),new Elcodi\Bundle\CoreBundle\ElcodiCoreBundle(),new Elcodi\Bundle\CurrencyBundle\ElcodiCurrencyBundle(),new Elcodi\Bundle\LanguageBundle\ElcodiLanguageBundle(),new Elcodi\Bundle\AttributeBundle\ElcodiAttributeBundle(),new Elcodi\Bundle\ProductBundle\ElcodiProductBundle(),new Elcodi\Bundle\MediaBundle\ElcodiMediaBundle(),
with symfony-standardAppKernel.php
doctrine_cache: providers: elcodi: type: arrayknp_gaufrette: adapters: images_storage_adapter: local: directory: %kernel.root_dir%/../web/images create: true filesystems: local: adapter: images_storage_adapter
with symfony-standardconfig.yml
$ php app/console do:fi:lo \ -‐-‐fixtures=vendor/elcodi/bamboo-‐fixtures/\
src/Fixtures/DataFixtures/ORM/Product
namespace Elcodi\TrainingBundle\Controller;use Symfony\Bundle\FrameworkBundle\Controller\Controller;use Symfony\Component\HttpFoundation\Response;class HelloController extends Controller{ public function helloAction() { $productRepository = $this->container ->get('elcodi.repository.product'); $storedProduct = $productRepository->find(1); $productObjectManager = $this->container ->get('elcodi.object_manager.product'); $newProduct = $this->container ->get('elcodi.factory.product')->create() ->setName('SymfonyCon ticket')->setSlug(''); $productObjectManager->persist($newProduct); $productObjectManager->flush(); return new Response($newProduct->getId()); }}
public function priceAction(){ $productObjectManager = $this->container ->get('elcodi.object_manager.product'); $productRepository = $this->container ->get('elcodi.repository.product'); $storedProduct = $productRepository->find(1); // Currency from a session based wrapper $currency = $this->container ->get('elcodi.currency_wrapper')->getCurrency(); // Money Value Object $newPrice = Money::create(1000, $currency); $storedProduct->setPrice($newPrice); $productObjectManager->flush(); return new Response("Changed price!"); }
public function changeMediaAction(){ $productRepository = $this->container ->get('elcodi.repository.product'); $storedProduct = $productRepository->find(1); // This can also be an instance of UploadedFile $file = new File('/tmp/my_image.jpg'); // Factors an Image and sets media meta-data $image = $this->container ->get('elcodi.image_manager') ->createImage($file); $imageObjectManager = $this->container ->get('elcodi.object_manager.image'); $imageObjectManager->persist($image); $imageObjectManager->flush($image); // Stores the image using the underlying // Gaufrette adapter $this->container ->get('elcodi.file_manager') ->uploadFile($image, $image->getContent(), false); $storedProduct->addImage($image); $storedProduct->setPrincipalImage($image); $this->container ->get('elcodi.object_manager.product') ->flush(); return new Response("Changed image!");}
extensibility?
2.simple Doctrine MappingMaking it… easy :)
premises
Assume that a lot of things will be overridden
Even an entity (Class, mapping, manager)
So… auto-mapping, you’re not welcomed!
Say hello to SimpleDoctrineMapping
auto-mapping = falseWooooo…
Why?
We want the responsibility of mapping our model, even for foreign required bundles (like FOS)
Each entity must be defined by a class, a mapping file and the manager assigned
Anyone should be able to modify this information in a very simple way
Compiler Pass
Using a compiler pass we add every single Entity configuration, even the Abstracts.
The CompilerPass must extend the foreign entities
Only one method available with 4 required parameters.
Bundle aliasing by short reference : @MyBundle
use Mmoreram\SimpleDoctrineMapping\CompilerPass\Abstracts\AbstractMappingCompilerPass;use Symfony\Component\DependencyInjection\ContainerBuilder;/** * Class MappingCompilerPass */class MappingCompilerPass extends AbstractMappingCompilerPass{ /** * You can modify the container here before it is dumped to PHP code. * * @param ContainerBuilder $container * * @api */ public function process(ContainerBuilder $container) { $this ->addEntityMapping( $container, 'default', 'Elcodi\Component\Product\Entity\Product', '@ElcodiProductBundle/Resources/config/doctrine/product.orm.yml', true ) ->addEntityMapping( $container, 'default', 'Elcodi\Component\Product\Entity\Category', '@ElcodiProductBundle/Resources/config/doctrine/category.orm.xml', true ); }}
It is not overridable!
Let’s think about a way of overriding Symfony Bundle values
…
Oh, yes! Configuration + Parameters! Let’s do that!
WHAT IF We could override anything?
use Mmoreram\SimpleDoctrineMapping\CompilerPass\Abstracts\AbstractMappingCompilerPass;use Symfony\Component\DependencyInjection\ContainerBuilder;/** * Class MappingCompilerPass */class MappingCompilerPass extends AbstractMappingCompilerPass{ /** * You can modify the container here before it is dumped to PHP code. * * @param ContainerBuilder $container * * @api */ public function process(ContainerBuilder $container) { $this ->addEntityMapping( $container, 'elcodi.core.product.entity.product.manager', 'elcodi.core.product.entity.product.class', 'elcodi.core.product.entity.product.mapping_file', 'elcodi.core.product.entity.product.enabled' ) ->addEntityMapping( $container, 'elcodi.core.product.entity.category.manager', 'elcodi.core.product.entity.category.class', 'elcodi.core.product.entity.category.mapping_file', 'elcodi.core.product.entity.category.enabled' ); }}
Propose a mapping of a Bundle’s entities
Change the namespace of an entity
Change the specific mapping file (xml or yml)
Change the associated manager class
And now you can
And even… disable it!
3.the cart systemEvents FTW!
A Cart represents the transient state of a purchase before it is committed.
An Order can be seen as a persisted snapshot of a Cart
Transitions raise events
rationale
new Elcodi\Bundle\CartBundle\ElcodiCartBundle(),new Elcodi\Bundle\UserBundle\ElcodiUserBundle(),new Elcodi\Bundle\GeoBundle\ElcodiGeoBundle()
more symfony-standardAppKernel.php
$ php app/console do:sc:up -‐-‐force
lots of new tables…
$ php app/console do:fi:lo \ -‐-‐fixtures=vendor/elcodi/bamboo-‐fixtures/\ src/Fixtures/DataFixtures/ORM/Customer \
-‐-‐append
public function addProductAction(){ $productRepository = $this->container ->get('elcodi.repository.product'); $storedProduct = $productRepository->find(1); $cart = $this->container ->get('elcodi.cart_wrapper')->loadCart(); $this->container ->get('elcodi.cart_manager') ->addProduct($cart, $storedProduct, 1); return new Response('Added to cart!');}
cartwrapper
Envelopes a Cart object and provides the logic to retrieve it
If the Customer has pending Carts, the last Cart form this collection is returned
If there is a Cart in session, it is associated with the Customer and is returned
When there is no Cart in session, a new one is factored
Overridable!
A new CartLine is factored with the information of the product
“Cart added” events are raised
“Cart loaded” events are raised
The latter is used by specific event listeners to recalculate prices and to perform a flush()
when i add a product
class ElcodiCartEvents{ const CART_PRELOAD = 'cart.preload'; const CART_ONLOAD = 'cart.onload'; const CART_ONEMPTY = 'cart.onempty'; const CART_INCONSISTENT = 'cart.inconsistent'; const CARTLINE_ONADD = 'cart_line.onadd'; const CARTLINE_ONEDIT = 'cart_line.onedit'; const CARTLINE_ONREMOVE = 'cart_line.onremove'; const ORDER_PRECREATED = 'order.precreated'; const ORDER_ONCREATED = 'order.oncreated'; const ORDERLINE_ONCREATED = 'order_line.oncreated'; const ORDER_STATE_PRECHANGE = 'order_state.prechange'; const ORDER_STATE_ONCHANGE = 'order_state.onchange'; const ORDERLINE_STATE_PRECHANGE = 'order_line_state.prechange'; const ORDERLINE_STATE_ONCHANGE = 'order_line_state.onchange'; }
public function createOrderAction(){ // CartWrapper will look for a customer in order // to "resolve" current cart $customer = $this->container ->get('elcodi.customer_wrapper')->loadCustomer(); $cart = $this->container ->get('elcodi.cart_wrapper')->loadCart(); $order = $this->container ->get('elcodi.cart_order_transformer') ->createOrderFromCart($cart); return new Response( sprintf('Order %d created!', $order->getId()) );}
how do I create an order?
more productivity tools?
4.AnnotationsTelling stories
Why?Always looking for a balance among smartness, efficiency and resolution.
Annotations are part of PHP. Why not?
Elcodi Core is a 3rd party project. Controllers as a services with no annotations
Bamboo is a project example on top of Elcodi, so Annotations would be nice
Symfony Best Practices. Thanks!
PDD principle
Why??Oh man! You’re coupling the controllers with the framework.
Ok. How many of you have ever change from one FW to another one with same controllers.
If we use abstract controllers, some logic data is hidden in another file.
Annotations tell us a story in a simple and clean way.
Controller Extra Bundle
Set of symfony framework controller annotations
Why ControllerExtraBundle instead of FrameworkExtraBundle?
Controller specific annotations and a platform for creating new ones.
We have the control of the repository. Important, right?
Documented with cool examples.
installing
"require": { "mmoreram/controller-extra-bundle": "~1.0", }
composer.json
@entity
Like FrameworkExtraBundle, but changes the way a class is defined
By namespace
By DIC parameter (yes, container dependency)
By factory with static or non-static method
By factory as a service with static or non-static method
use Mmoreram\ControllerExtraBundle\Annotation\Entity as EntityAnnotation;use Symfony\Component\HttpKernel\Tests\Controller;class AnnotatedController extends Controller{ /** * Simple controller method * * This Controller matches pattern /user/edit/{id}/{username} * * @EntityAnnotation( * class = { * "factory" = my.bundle.factory.myentity_factory, * "method" = "create", * "static" = true, * }, * name = "user", * mapping = { * "id": "~id~", * "username": "~username~" * } * ) */ public function indexAction(User $user) { }}
@form
Creates a FormType, a Form or FormView (depending on the parameter type-hinting)
Can be defined with the namespace or with the alias
Can handle the request with a previously defined entity (With @entity or @paramConverter)
Can validate the form and inject the result as an action parameter
use Mmoreram\ControllerExtraBundle\Annotation\Form as FormAnnotation;use Symfony\Component\HttpKernel\Tests\Controller;class AnnotatedController extends Controller{ /** * Simple controller method * * @Route( * path = "/user/{id}", * name = "view_user" * ) * @ParamConverter("user", class="MmoreramCustomBundle:User") * @FormAnnotation( * class = "user_type", * entity = "user" * handleRequest = true, * name = "userForm", * validate = "isValid", * ) */ public function indexAction(User $user, Form $userForm, $isValid) { }
@paginator
Given an URL, injects a Paginator
In current implementation, some extra info can be also injected, like total available pages.
Pagerfanta compatible (not used in Elcodi)
use Mmoreram\ControllerExtraBundle\Annotation\Paginator;use Symfony\Component\HttpKernel\Tests\Controller;class AnnotatedController extends Controller{ /** * Simple controller method * * @Paginator( * class = "MmoreramCustomBundle:User", * wheres = { * {"x", "enabled", "=", true}, * {"x", "age", ">", “~age~”}, * {"x", "name", "LIKE", "Efervescencio"}, * } * ) */ public function indexAction(Paginator $paginator) { } }
summing it up
Elcodi is a set of tools to speed up development
It helps you scale out your ideas
Pragmatic driven
Baked with♥
questions?
ten-queue!See you in Github!