SlideShare a Scribd company logo
1 of 116
Download to read offline
How Kris Writes Symfony Apps
       @kriswallsmith • February 9, 2013
About Me
@kriswallsmith.net

•   Born, raised, & live in Portland

•   10+ years of experience

•   Lead Architect at OpenSky

•   Open source fanboy
brewcycleportland.com
assetic
Buzz
Spork
Go big or go home.
Getting Started
composer create-project 
    symfony/framework-standard-edition 
    opti-grab/ 2.2.x-dev
-   "doctrine/orm": "~2.2,>=2.2.3",
-   "doctrine/doctrine-bundle": "1.2.*",
+   "doctrine/mongodb-odm-bundle": "3.0.*",
+   "jms/serializer-bundle": "1.0.*",
./app/console generate:bundle 
    --namespace=OptiGrab/Bundle/MainBundle
assetic:
    debug:             %kernel.debug%
    use_controller:    false
    bundles:           [ MainBundle ]
    filters:
         cssrewrite:   ~
         uglifyjs2:    { compress: true, mangle: true }
         uglifycss:    ~
jms_di_extra:
    locations:
        bundles:
            - MainBundle
public function registerContainerConfiguration(LoaderInterface $loader)
{
    $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');

    // load local_*.yml or local.yml
    if (
         file_exists($file = __DIR__.'/config/local_'.$this->getEnvironment().'.yml')
         ||
         file_exists($file = __DIR__.'/config/local.yml')
    ) {
         $loader->load($file);
    }
}
MongoDB
Treat your model like a princess.
She gets her own wing
   of the palace…
doctrine_mongodb:
    auto_generate_hydrator_classes: %kernel.debug%
    auto_generate_proxy_classes:      %kernel.debug%
    connections: { default: ~ }
    document_managers:
        default:
            connection: default
            database:     optiGrab
            mappings:
                 model:
                     type:    annotation
                     dir:     %src_dir%/OptiGrab/Model
                     prefix: OptiGrabModel
                     alias: Model
// repo for src/OptiGrab/Model/Widget.php
$repo = $this->dm->getRepository('Model:User');
…doesn't do any work…
use OptiGrabBundleMainBundleCanonicalizer;

public function setUsername($username)
{
    $this->username = $username;

    $canonicalizer = Canonicalizer::instance();
    $this->usernameCanonical = $canonicalizer->canonicalize($username);
}
use OptiGrabBundleMainBundleCanonicalizer;

public function setUsername($username, Canonicalizer $canonicalizer)
{
    $this->username = $username;
    $this->usernameCanonical = $canonicalizer->canonicalize($username);
}
…and is unaware of the work
  being done around her.
public function setUsername($username)
{
    // a listener will update the
    // canonical username
    $this->username = $username;
}
No query builders
outside of repositories
class WidgetRepository extends DocumentRepository
{
    public function findByUser(User $user)
    {
        return $this->createQueryBuilder()
            ->field('userId')->equals($user->getId())
            ->getQuery()
            ->execute();
    }

    public function updateDenormalizedUsernames(User $user)
    {
        $this->createQueryBuilder()
            ->update()
            ->multiple()
            ->field('userId')->equals($user->getId())
            ->field('userName')->set($user->getUsername())
            ->getQuery()
            ->execute();
    }
}
Eager id creation
public function __construct()
{
    $this->id = (string) new MongoId();
}
public function __construct()
{
    $this->id = (string) new MongoId();
    $this->createdAt = new DateTime();
    $this->widgets = new ArrayCollection();
}
Remember your
clone constructor
$foo = new Foo();
$bar = clone $foo;
public function __clone()
{
    $this->id = (string) new MongoId();
    $this->createdAt = new DateTime();
    $this->widgets = new ArrayCollection(
        $this->widgets->toArray()
    );
}
public function __construct()
{
    $this->id = (string) new MongoId();
    $this->createdAt = new DateTime();
    $this->widgets = new ArrayCollection();
}

public function __clone()
{
    $this->id = (string) new MongoId();
    $this->createdAt = new DateTime();
    $this->widgets = new ArrayCollection(
        $this->widgets->toArray()
    );
}
Only flush from the controller
public function theAction(Widget $widget)
{
    $this->get('widget_twiddler')
         ->skeedaddle($widget);
    $this->flush();
}
Save space on field names
/** @ODMString(name="u") */
private $username;

/** @ODMString(name="uc") @ODMUniqueIndex */
private $usernameCanonical;
public function getUsername()
{
    return $this->username ?: $this->usernameCanonical;
}

public function setUsername($username)
{
    if ($username) {
        $this->usernameCanonical = strtolower($username);
        $this->username = $username === $this->usernameCanonical ? null : $username;
    } else {
        $this->usernameCanonical = null;
        $this->username = null;
    }
}
No proxy objects
/** @ODMReferenceOne(targetDocument="User") */
private $user;
public function getUser()
{
    if ($this->userId && !$this->user) {
        throw new UninitializedReferenceException('user');
    }

    return $this->user;
}
Mapping Layers
What is a mapping layer?
A mapping layer is thin
Thin controller, fat model…
Is Symfony an MVC framework?
Symfony is an HTTP framework
Application Land
  Controller
  HTTP Land
The controller maps from
HTTP-land to application-land.
What about the model?
public function registerAction()
{
    // ...
    $user->sendWelcomeEmail();
    // ...
}
public function registerAction()
{
    // ...
    $mailer->sendWelcomeEmail($user);
    // ...
}
Persistence Land
    Model
Application Land
The model maps from
application-land to persistence-land.
Persistence Land
     Model
Application Land
  Controller
  HTTP Land
Who lives in application land?
Thin controller, thin model…
      Fat service layer!
Application Events
Use lots of them
That happened.
/** @DIObserve("user.username_change") */
public function onUsernameChange(UserEvent $event)
{
    $user = $event->getUser();
    $dm   = $event->getDocumentManager();

    $dm->getRepository('Model:Widget')
       ->updateDenormalizedUsernames($user);
}
Unit of Work
public function onFlush(OnFlushEventArgs $event)
{
    $dm = $event->getDocumentManager();
    $uow = $dm->getUnitOfWork();

    foreach ($uow->getIdentityMap() as $class => $docs) {
        if (self::checkClass('OptiGrabModelUser', $class)) {
            foreach ($docs as $doc) {
                $this->processUserFlush($dm, $doc);
            }
        } elseif (self::checkClass('OptiGrabModelWidget', $class)) {
            foreach ($docs as $doc) {
                $this->processWidgetFlush($dm, $doc);
            }
        }
    }
}
private function processUserFlush(DocumentManager $dm, User $user)
{
    $uow     = $dm->getUnitOfWork();
    $meta    = $dm->getClassMetadata('Model:User');
    $changes = $uow->getDocumentChangeSet($user);

    if (isset($changes['id'][1])) {
        $this->dispatcher->dispatch(UserEvents::CREATE, new UserEvent($dm, $user));
    }

    if (isset($changes['usernameCanonical'][0]) && null !== $changes['usernameCanonical'][0]) {
        $this->dispatcher->dispatch(UserEvents::USERNAME_CHANGE, new UserEvent($dm, $user));
    }

    if ($followedUsers = $meta->getFieldValue($user, 'followedUsers')) {
        foreach ($followedUsers->getInsertDiff() as $otherUser) {
            $this->dispatcher->dispatch(
                UserEvents::FOLLOW_USER,
                new UserUserEvent($dm, $user, $otherUser)
            );
        }

        foreach ($followedUsers->getDeleteDiff() as $otherUser) {
            // ...
        }
    }
}
/** @DIObserve("user.create") */
public function onUserCreate(UserEvent $event)
{
    $user = $event->getUser();

    $activity = new Activity();
    $activity->setActor($user);
    $activity->setVerb('register');
    $activity->setCreatedAt($user->getCreatedAt());

    $this->dm->persist($activity);
}
/** @DIObserve("user.create") */
public function onUserCreate(UserEvent $event)
{
    $dm   = $event->getDocumentManager();
    $user = $event->getUser();

    $widget = new Widget();
    $widget->setUser($user);

    $dm->persist($widget);

    // manually notify the event
    $event->getDispatcher()->dispatch(
        WidgetEvents::CREATE,
        new WidgetEvent($dm, $widget)
    );
}
/** @DIObserve("user.follow_user") */
public function onFollowUser(UserUserEvent $event)
{
    $event->getUser()
          ->getStats()
          ->incrementFollowedUsers(1);
    $event->getOtherUser()
          ->getStats()
          ->incrementFollowers(1);
}
Two event classes per model
• @MainBundleUserEvents: encapsulates event name constants
  such as UserEvents::CREATE and
  UserEvents::CHANGE_USERNAME

• @MainBundleEventUserEvent: base event object, accepts
  $dm and $user arguments

• @MainBundleWidgetEvents…
• @MainBundleEventWidgetEvent…
$event = new UserEvent($dm, $user);
$dispatcher->dispatch(UserEvents::CREATE, $event);
Delegate work to clean, concise,
 single-purpose event listeners
Contextual Configuration
Save your future self a headache
# @MainBundle/Resources/config/widget.yml
services:
    widget_twiddler:
        class: OptiGrabBundleMainBundleWidgetTwiddler
        arguments:
            - @event_dispatcher
            - @?logger
/** @DIService("widget_twiddler") */
class Twiddler
{
    /** @DIInjectParams */
    public function __construct(
        EventDispatcherInterface $dispatcher,
        LoggerInterface $logger = null)
    {
        // ...
    }
}
services:
    # aliases for auto-wiring
    container: @service_container
    dm: @doctrine_mongodb.odm.document_manager
    doctrine: @doctrine_mongodb
    dispatcher: @event_dispatcher
    security: @security.context
JMSDiExtraBundle
require.js
<script src="{{ asset('js/lib/require.js') }}"></script>
<script>
require.config({
    baseUrl: "{{ asset('js') }}",
    paths: {
         "jquery": "//ajax.googleapis.com/.../jquery.min",
         "underscore": "lib/underscore",
         "backbone": "lib/backbone"
    },
    shim: {
         "jquery": { exports: "jQuery" },
         "underscore": { exports: "_" },
         "backbone": {
             deps: [ "jquery", "underscore" ],
             exports: "Backbone"
         }
    }
})
require([ "main" ])
</script>
// web/js/model/user.js
define(
    [ "underscore", "backbone" ],
    function(_, Backbone) {
        var tmpl = _.template("<%- first %> <%- last %>")
        return Backbone.Model.extend({
            name: function() {
                return tmpl({
                    first: this.get("first_name"),
                    last: this.get("last_name")
                })
            }
        })
    }
)
{% block head %}
<script>
require(
    [ "view/user", "model/user" ],
    function(UserView, User) {
         var view = new UserView({
             model: new User({{ user|serialize|raw }}),
             el: document.getElementById("user")
         })
    }
)
</script>
{% endblock %}
Dependencies


•   model: backbone, underscore

•   view: backbone, jquery

•   template: model, view
{% javascripts
    "js/lib/jquery.js" "js/lib/underscore.js"
    "js/lib/backbone.js" "js/model/user.js"
    "js/view/user.js"
    filter="?uglifyjs2" output="js/packed/user.js" %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}

<script>
var view = new UserView({
    model: new User({{ user|serialize|raw }}),
    el: document.getElementById("user")
})
</script>
Unused dependencies
 naturally slough off
JMSSerializerBundle
{% block head %}
<script>
require(
    [ "view/user", "model/user" ],
    function(UserView, User) {
         var view = new UserView({
             model: new User({{ user|serialize|raw }}),
             el: document.getElementById("user")
         })
    }
)
</script>
{% endblock %}
/** @ExclusionPolicy("ALL") */
class User
{
    private $id;

    /** @Expose */
    private $firstName;

    /** @Expose */
    private $lastName;
}
Miscellaneous
When to create a new bundle
Lots of classes pertaining to
        one feature
{% include 'MainBundle:Account/Widget:sidebar.html.twig' %}
{% include 'AccountBundle:Widget:sidebar.html.twig' %}
Access Control
The Symfony ACL is for
 arbitrary permissions
Encapsulate access logic in
  custom voter classes
/** @DIService(public=false) @DITag("security.voter") */
class WidgetVoter implements VoterInterface
{
    public function supportsAttribute($attribute)
    {
        return 'OWNER' === $attribute;
    }

    public function supportsClass($class)
    {
        return 'OptiGrabModelWidget' === $class
            || is_subclass_of($class, 'OptiGrabModelWidget');
    }

    public function vote(TokenInterface $token, $widget, array $attributes)
    {
        // ...
    }
}
public function vote(TokenInterface $token, $map, array $attributes)
{
    $result = VoterInterface::ACCESS_ABSTAIN;

    if (!$this->supportsClass(get_class($map))) {
        return $result;
    }

    foreach ($attributes as $attribute) {
        if (!$this->supportsAttribute($attribute)) {
            continue;
        }

        $result = VoterInterface::ACCESS_DENIED;
        if ($token->getUser() === $map->getUser()) {
            return VoterInterface::ACCESS_GRANTED;
        }
    }

    return $result;
}
/** @SecureParam(name="widget", permissions="OWNER") */
public function editAction(Widget $widget)
{
    // ...
}
{% if is_granted('OWNER', widget) %}
{# ... #}
{% endif %}
Only mock interfaces
interface FacebookInterface
{
    function getUser();
    function api();
}

/** @DIService("facebook") */
class Facebook extends BaseFacebook implements FacebookInterface
{
    // ...
}
$facebook = $this->getMock('OptiGrabBundleMainBundleFacebookFacebookInterface');
$facebook->expects($this->any())
    ->method('getUser')
    ->will($this->returnValue(123));
Questions?
@kriswallsmith.net



        joind.in/8024


      Thank You!

More Related Content

What's hot

Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingSamuel ROZE
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDAleix Vergés
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form componentSamuel ROZE
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patternsSamuel ROZE
 
DOM Scripting Toolkit - jQuery
DOM Scripting Toolkit - jQueryDOM Scripting Toolkit - jQuery
DOM Scripting Toolkit - jQueryRemy Sharp
 
An Introduction to Jquery
An Introduction to JqueryAn Introduction to Jquery
An Introduction to JqueryPhil Reither
 
Building Large jQuery Applications
Building Large jQuery ApplicationsBuilding Large jQuery Applications
Building Large jQuery ApplicationsRebecca Murphey
 
Functionality Focused Code Organization
Functionality Focused Code OrganizationFunctionality Focused Code Organization
Functionality Focused Code OrganizationRebecca Murphey
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICKonstantin Kudryashov
 
amsterdamjs - jQuery 1.5
amsterdamjs - jQuery 1.5amsterdamjs - jQuery 1.5
amsterdamjs - jQuery 1.5mennovanslooten
 
Advanced jQuery
Advanced jQueryAdvanced jQuery
Advanced jQuerysergioafp
 
jQuery 1.7 Events
jQuery 1.7 EventsjQuery 1.7 Events
jQuery 1.7 Eventsdmethvin
 
Beyond the DOM: Sane Structure for JS Apps
Beyond the DOM: Sane Structure for JS AppsBeyond the DOM: Sane Structure for JS Apps
Beyond the DOM: Sane Structure for JS AppsRebecca Murphey
 
Jqeury ajax plugins
Jqeury ajax pluginsJqeury ajax plugins
Jqeury ajax pluginsInbal Geffen
 
Kick start with j query
Kick start with j queryKick start with j query
Kick start with j queryMd. Ziaul Haq
 

What's hot (20)

Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event Sourcing
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form component
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
Bacbkone js
Bacbkone jsBacbkone js
Bacbkone js
 
jQuery: Events, Animation, Ajax
jQuery: Events, Animation, AjaxjQuery: Events, Animation, Ajax
jQuery: Events, Animation, Ajax
 
DOM Scripting Toolkit - jQuery
DOM Scripting Toolkit - jQueryDOM Scripting Toolkit - jQuery
DOM Scripting Toolkit - jQuery
 
An Introduction to Jquery
An Introduction to JqueryAn Introduction to Jquery
An Introduction to Jquery
 
Building Large jQuery Applications
Building Large jQuery ApplicationsBuilding Large jQuery Applications
Building Large jQuery Applications
 
Functionality Focused Code Organization
Functionality Focused Code OrganizationFunctionality Focused Code Organization
Functionality Focused Code Organization
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
 
amsterdamjs - jQuery 1.5
amsterdamjs - jQuery 1.5amsterdamjs - jQuery 1.5
amsterdamjs - jQuery 1.5
 
Special Events
Special EventsSpecial Events
Special Events
 
Advanced jQuery
Advanced jQueryAdvanced jQuery
Advanced jQuery
 
jQuery 1.7 Events
jQuery 1.7 EventsjQuery 1.7 Events
jQuery 1.7 Events
 
Beyond the DOM: Sane Structure for JS Apps
Beyond the DOM: Sane Structure for JS AppsBeyond the DOM: Sane Structure for JS Apps
Beyond the DOM: Sane Structure for JS Apps
 
Jqeury ajax plugins
Jqeury ajax pluginsJqeury ajax plugins
Jqeury ajax plugins
 
Dojo Confessions
Dojo ConfessionsDojo Confessions
Dojo Confessions
 
Jquery Fundamentals
Jquery FundamentalsJquery Fundamentals
Jquery Fundamentals
 
Kick start with j query
Kick start with j queryKick start with j query
Kick start with j query
 

Similar to How Kris Writes Symfony Apps

Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologyDaniel Knell
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleHugo Hamon
 
ZF2 for the ZF1 Developer
ZF2 for the ZF1 DeveloperZF2 for the ZF1 Developer
ZF2 for the ZF1 DeveloperGary Hockin
 
Drupal 8 Services And Dependency Injection
Drupal 8 Services And Dependency InjectionDrupal 8 Services And Dependency Injection
Drupal 8 Services And Dependency InjectionPhilip Norton
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Leonardo Proietti
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsMichael Peacock
 
Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12Jakub Zalas
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsSam Hennessy
 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016Kacper Gunia
 
Doctrine MongoDB Object Document Mapper
Doctrine MongoDB Object Document MapperDoctrine MongoDB Object Document Mapper
Doctrine MongoDB Object Document MapperJonathan Wage
 
Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?Julien Vinber
 
Refactoring using Codeception
Refactoring using CodeceptionRefactoring using Codeception
Refactoring using CodeceptionJeroen van Dijk
 
Lithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate FrameworksLithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate FrameworksNate Abele
 
Version Control with Puppet
Version Control with PuppetVersion Control with Puppet
Version Control with PuppetPuppet
 
PuppetCamp SEA 1 - Version Control with Puppet
PuppetCamp SEA 1 - Version Control with PuppetPuppetCamp SEA 1 - Version Control with Puppet
PuppetCamp SEA 1 - Version Control with PuppetWalter Heck
 

Similar to How Kris Writes Symfony Apps (20)

Unittests für Dummies
Unittests für DummiesUnittests für Dummies
Unittests für Dummies
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
 
Migrare da symfony 1 a Symfony2
 Migrare da symfony 1 a Symfony2  Migrare da symfony 1 a Symfony2
Migrare da symfony 1 a Symfony2
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
 
ZF2 for the ZF1 Developer
ZF2 for the ZF1 DeveloperZF2 for the ZF1 Developer
ZF2 for the ZF1 Developer
 
BEAR DI
BEAR DIBEAR DI
BEAR DI
 
Drupal 8 Services And Dependency Injection
Drupal 8 Services And Dependency InjectionDrupal 8 Services And Dependency Injection
Drupal 8 Services And Dependency Injection
 
Symfony2 your way
Symfony2   your waySymfony2   your way
Symfony2 your way
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
 
Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12
 
Symfony tips and tricks
Symfony tips and tricksSymfony tips and tricks
Symfony tips and tricks
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016
 
Doctrine MongoDB Object Document Mapper
Doctrine MongoDB Object Document MapperDoctrine MongoDB Object Document Mapper
Doctrine MongoDB Object Document Mapper
 
Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?
 
Refactoring using Codeception
Refactoring using CodeceptionRefactoring using Codeception
Refactoring using Codeception
 
Lithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate FrameworksLithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate Frameworks
 
Version Control with Puppet
Version Control with PuppetVersion Control with Puppet
Version Control with Puppet
 
PuppetCamp SEA 1 - Version Control with Puppet
PuppetCamp SEA 1 - Version Control with PuppetPuppetCamp SEA 1 - Version Control with Puppet
PuppetCamp SEA 1 - Version Control with Puppet
 

More from Kris Wallsmith

Assetic (Symfony Live Paris)
Assetic (Symfony Live Paris)Assetic (Symfony Live Paris)
Assetic (Symfony Live Paris)Kris Wallsmith
 
Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)Kris Wallsmith
 
Introducing Assetic: Asset Management for PHP 5.3
Introducing Assetic: Asset Management for PHP 5.3Introducing Assetic: Asset Management for PHP 5.3
Introducing Assetic: Asset Management for PHP 5.3Kris Wallsmith
 
Doctrine MongoDB ODM (PDXPHP)
Doctrine MongoDB ODM (PDXPHP)Doctrine MongoDB ODM (PDXPHP)
Doctrine MongoDB ODM (PDXPHP)Kris Wallsmith
 
Advanced symfony Techniques
Advanced symfony TechniquesAdvanced symfony Techniques
Advanced symfony TechniquesKris Wallsmith
 
A Practical Introduction to Symfony2
A Practical Introduction to Symfony2A Practical Introduction to Symfony2
A Practical Introduction to Symfony2Kris Wallsmith
 

More from Kris Wallsmith (11)

The View From Inside
The View From InsideThe View From Inside
The View From Inside
 
Assetic (Zendcon)
Assetic (Zendcon)Assetic (Zendcon)
Assetic (Zendcon)
 
Assetic (OSCON)
Assetic (OSCON)Assetic (OSCON)
Assetic (OSCON)
 
Assetic (Symfony Live Paris)
Assetic (Symfony Live Paris)Assetic (Symfony Live Paris)
Assetic (Symfony Live Paris)
 
Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)
 
Introducing Assetic: Asset Management for PHP 5.3
Introducing Assetic: Asset Management for PHP 5.3Introducing Assetic: Asset Management for PHP 5.3
Introducing Assetic: Asset Management for PHP 5.3
 
Doctrine MongoDB ODM (PDXPHP)
Doctrine MongoDB ODM (PDXPHP)Doctrine MongoDB ODM (PDXPHP)
Doctrine MongoDB ODM (PDXPHP)
 
Advanced symfony Techniques
Advanced symfony TechniquesAdvanced symfony Techniques
Advanced symfony Techniques
 
A Practical Introduction to Symfony2
A Practical Introduction to Symfony2A Practical Introduction to Symfony2
A Practical Introduction to Symfony2
 
Symfony 2
Symfony 2Symfony 2
Symfony 2
 
Symfony in the Cloud
Symfony in the CloudSymfony in the Cloud
Symfony in the Cloud
 

Recently uploaded

Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rick Flair
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxLoriGlavin3
 
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxUse of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxLoriGlavin3
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
What is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfWhat is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfMounikaPolabathina
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersRaghuram Pandurangan
 
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxLoriGlavin3
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESmohitsingh558521
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embeddingZilliz
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 

Recently uploaded (20)

Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptx
 
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxUse of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
What is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfWhat is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdf
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information Developers
 
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embedding
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 

How Kris Writes Symfony Apps