DÉVELOPPER AVEC LE SYLIUSRESOURCEBUNDLE
QUI SUIS-JE ?
Arnaud Langlade (@_aRn0D)
Développeur Symfony chez Clever Age
www.clever-age.com / @CleverAge
SYLIUS
Framework E-commerce créé par Paweł Jędrzejewski
Ensemble de bundles Symfony et composants PHP e-commerce
Sylius Starndard Edition
Quelques chiffres : ~200 contributeurs / ~1700 stars
SYLIUSRESOURCEBUNDLE
Le SyliusResourceBundle vous permet de gérer rapidement et simplement vos ressources et de les
exposer via API REST.
Il n'y a pas que des composants e-commerce dans Sylius !
GESTION DES RESSOURCES? Z'AVEZ DIS CRUD?
CRUD = Create, Read, Update, Delete
UN CRUD, COMMENT ÇA FONCTIONNE?
POURQUOI LA CRÉATION DE CE BUNDLE?
Le back office de Sylius est composé d'énormement de CRUDs
Eviter la duplication de code parce que c'est mal!
Développer plus vite en automatisant des tâches
UNE SOLUTION? RESOURCECONTROLLER?
Création du ResourceController :
C'est un contrôleur générique
Plus de code, il est intialisé via une configuration
Il étend the FOSRestController
Utilisation du EventDispatcher (répartiteur d'évènement) :
Il permet de personnaliser les actions
Solution plus flexible
QUELS SONT ORM/ODM SUPPORTÉS ?
Doctrine ORM : Sylius l'utilise par défaut
Doctrine Phpcr-ODM : Sylius intègre le CMF pour gérer des contenus
Doctrine Mongodb-ODM
Bientôt sûrement plus !
ATTENTION!
Ce n'est pas un admin generator!
Il faut créer vos formulaires, vos templates et le routing (pour le moment!)
CRÉER SON CRUD EN QUELQUES MINUTES !
Par exemple, créons un CRUD pour gérer des clients (customer).
CONFIGURONS NOS RESSOURCES
sylius_resource:
    resources:
        myapp.customer:
            driver: doctrine/orm
            classes:
                model: AppBundleEntityCustomer
                repository: SyliusBundleResourceBundleDoctrineORMEntityRepository
            templates: WebBundle:Backend/Customer
        myapp.address:
            # ...
MAIS QUE SE PASSE T'IL ?
$ php app/console container:debug | grep customer
myapp.manager.customer    alias for "doctrine.orm.default_entity_manager"
myapp.controller.customer container SyliusBundleResourceBundleControllerResourceController
myapp.repository.customer container SyliusBundleResourceBundleDoctrineORMEntityRepository
$ php app/console container:debug ­­parameters
sylius.config.classes {"myapp.customer": {...}}
CONFIGURONS NOS RESSOURCES
sylius_resource:
    resources:
        myapp.customer:
            driver: doctrine/orm
            classes:
                model: AppBundleEntityCustomer
                controller: AppBundleControllerCustomerController
                repository: AppBundleRepositoryCustomerRepository
            templates: WebBundle:Backend/Customer
CRÉONS NOTRE MODÈLE
# AppBundleEntityCustomer.php;
/**
 * @ORMEntity
 * @ORMTable(name="customer")
 */
class Customer
{
    /**
     * @ORMColumn(type="string", length=100)
     */
    protected $firstName;
    /**
     * @ORMColumn(type="string", length=100)
     */
    protected $lastName;
}
CRÉONS NOTRE FORMULAIRE
// AppBundleFormTypeCustomerType.php;
class CustomerType extends AbstractResourceType
{
    public function getName()
    {
        return 'myapp_customer';
    }
}
Le formulaire doit être défini en tant que service
Pattern du nom de formulaire : nom-application_resource
AbstractResourceType permet de configurer le data_class et le validation_group
CRÉONS NOS TEMPLATES
{# create.html.twig ou update.html.twig #}
<form method="POST" action="...">
    {{ form_widget(form) }}
</form>
{# show.html.twig #}
<div>
    <p>{{ customer.firstname }}</p>
    <p>{{ customer.lastname }}</p>
</div>
{# index.html.twig #}
{% for customer in customers %}
    {{ customer.fistname }} {{ customer.lastname }}
{% endfor %}
CONFIGURONS NOS ROUTES
# app/routing.yml
myapp_customer_index:
    pattern: /customer
    defaults:
        _controller: myapp.controller.customer:indexAction
Pattern des clés des routes : nom-application_resource_action
Ne pas oublier que les contrôleurs sont définis en tant que service
Actions : index, show, create, update, delete, moveUp, moveDown, revert ou updateState
ET PAF ! ÇA FAIT DES CHOCAPICS !
Notre CRUD est prêt à l'emploi !! On crée notre API ?
EXPOSER SES CLIENTS VIA API REST
Configurer le FOSRestBundle
# app/config.yml
fos_rest:
    format_listener:
        rules:
            ­ { path: '^/', priorities: ['html', 'json'], fallback_format: html}
Le ResourceController retourne le données dans le format souhaité
GET /customer/57 HTTP/1.1
Host: myshop.com
Accept: application/json
CONFIGURER LE SÉRIALISEUR
# Resources/config/serializer/Entity.Customer.yml
AppBundleEntityCustomer:
    exclusion_policy: ALL
    properties:
        firstName:
            expose: true
            type: string
        lastName:
            expose: true
            type: string
        relations:
            ­ rel: address
            href:
                route: myapp_address_show
                parameters:
                    id: expr(object.getAddress().getId())
HTTP/1.1 200 OK
Content­Type: application/json;
{
    "id": 2,
    "firstName": "Arnaud",
    "lastName": "Langlade",
    "_links": {
        "self": {
            "href": "/address/2"
        }
    }
}
C'EST TOUT ?
Lionframe (Rapid RESTful API Development)
Génération automatique des formulaires et du routing
PLUS DE FLEXIBILITÉ ?
Le comportement des méthodes du ResourceController est configurable
CONFIGURER LES METHODES DU RESOURCECONTROLLER
Ajouter une entrée _sylius dans le tableau defaults des routes
# app/routing.yml
myapp_customer_create:
    defaults:
        _sylius:
            template: WebBundle:Backend/Customer:custom_create.html.twig
RÉDIRIGER L'UTILISATEUR
# app/routing.yml
myapp_product_create:
    pattern: /new
    methods: [GET, POST]
        _controller: myapp.controller.product:createAction
        _sylius:
            redirect: myapp_product_index
            # Ou
            redirect:
                route: myapp_product_show
                parameters: { name: resource.sku }
RÉCUPÉRER DES DONNÉES DANS LA BDD
# app/routing.yml
myapp_customer_index:
    pattern: /
    methods: [GET]
    defaults:
        _controller: myapp.controller.customer:indexAction
        _sylius:
            # $repository­>findBy(["group" => 'my_group'])
            criteria:
                group: my_group
            # $request­>get('criteria')
            filterable: true
RÉCUPÉRER DES DONNÉES DANS LA BDD
# app/routing.yml
myapp_customer_index:
    pattern: /
    methods: [GET]
    defaults:
        _controller: myapp.controller.customer:indexAction
        _sylius:
            # $repository­>findByFilter($request­>get('filter'))
            repository:
                method: findByFilter
                arguments: [$filter]
LISTER SES RESSOURCES
# app/routing.yml
myapp_customer_index:
    pattern: /
    methods: [GET]
    defaults:
        _controller: myapp.controller.customer:indexAction
        _sylius:
            # Trie
            sorting:
                updatedAt: desc # Ou asc
            # $request­>get('sorting');
            sortable: true
            # Paginate
            paginate: 50
MOTEUR D'EXPRESSION
# app/routing.yml
myapp_order_index:
    path: /orders
    methods: [GET]
    defaults:
        _controller: app.controller.order:indexAction
        _sylius:
            repository:
                # $repository­>findOrderByCustomer([$customer]);
                method: findOrderByCustomer
                arguments: ["expr:service('security.context').getToken().getUser()"]
VOUS VOULEZ MUTUALISER VOTRE CODE ?
SYLIUS FONCTIONNE AVEC DES BUNDLES
Ils doivent être facilement étendables
Ils peuvent supporter plusieurs "drivers" (ORM/ODM)
Ils ne doivent être couplés les uns aux autres
LA CONFIGURATION SÉMANTIQUE
customer_bundle:
    driver: doctrine/orm
    templates:
        customer: CustomerBundle:Backend/Customer
        address: ...
    validation_groups:
        customer: [myapp]
        address: ...
    classes:
        customer:
            model: MyappCustomerBundleModelCustomer
            controller: SyliusBundleResourceBundleControllerResourceController
            repository: SyliusBundleResourceBundleDoctrineORMEntityRepository
            form:
                default: MyappCustomerBundleFormTypeCustomerType
                choice: MyappCustomerBundleFormTypeCustomerChoiceType
        address: ...
MAIS QUE SE PASSE T'IL?
$ php app/console container:debug | grep customer
myapp.controller.customer container SyliusBundleResourceBundleControllerResourceController
myapp.manager.customer    n/a       alias for doctrine.orm.default_entity_manager
myapp.repository.customer container SyliusBundleResourceBundleDoctrineORMEntityRepository
myapp.form.type.customer  container myappCustomerBundleFormTypeCustomerType
$ php app/console container:debug ­­parameters | grep customer
myapp.model.customer.class          myappCustomerBundleModelCustomer
myapp.model.customer.class          myappCustomerBundleModelCustomer
myapp.controller.customer.class     SyliusBundleResourceBundleControllerResourceController
myapp.repository.customer.class     SyliusBundleResourceBundleDoctrineORMEntityRepository
myapp.form.type.customer.class      myappCustomerBundleFormTypeCustomerType
myapp.validation_group.customer     ["myapp"]
myapp_customer.driver               doctrine/orm
ETENDRE FACILEMENT VOTRE BUNDLE
Utiliser l'injecteur de dépendences (Dependency Injector)
Déclarer votre classe en tant mapped-superclass (évènement loadClassMetadata)
GÉRER PLUSIEURS DRIVERS
Créer votre "Doctrine Mapping Driver"
Fournir plusieurs implementations Doctrine pour un modèle
Vos documents et entités sont dans le même namespace
LIMITER LE COUPLAGE DE VOS BUNDLES
Utiliser le Resolve Target Entity Listener
Définir des relations entre différentes entités sans les écrire en dur
Il ré-écrit les paramètres targetEntity dans le mapping de votre modèle
<!­­ Resources/config/doctrine/order.xml ­­>
<one­to­many field="orders" target­entity="SyliusComponentOrderModelOrderInterface">
    <!­­ ... ­­>
</one­to­many>
VOUS ÊTES ÉQUIPÉS POUR CONSTRUIRE VOS BUNDLES!
GO TO THE FUTURE!
Refactoring de la SyliusResourceExtension
Rendre le système plus flexible
Uniformiser la configuration
De nouveaux FormType ?
Un datagrid ?
VENEZ CONTRIBUER!
Merci à tous les contributeurs!
N'hésister pas à nous soumettre vos PRs...
... surtout si vous aimez écrire de la doc :D !
MERCI! QUESTIONS ?
Arnaud Langlade
Twiter @_aRn0D
Sylius : www.sylius.org

Développer avec le sylius resourcebundle (Symfony live Paris 2015)