Symfony 1.3 + Doctrine 1.2




                Symfony 1.3
                     +
                Doctrine 1.2

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   1
Symfony 1.3 + Doctrine 1.2




               Continued Evolution
                 of Symfony 1.x



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   2
Symfony 1.3 + Doctrine 1.2




       Lots of new features,
    enhancements and bug fixes.



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   3
Symfony 1.3 + Doctrine 1.2




     Out with the old and in with
              the new!



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   4
Symfony 1.3 + Doctrine 1.2




      Doctrine is the default ORM




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   5
Symfony 1.3 + Doctrine 1.2




              Propel is deprecated




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   6
Symfony 1.3 + Doctrine 1.2




 Propel is deprecated in 1.3 but
 not removed. It will be dropped
   completely in symfony 2.0



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   7
Symfony 1.3 + Doctrine 1.2




      Still want to use Propel? :)
       Generate your project with the new --orm option



         $ php /path/to/symfony generate:project foo --orm=Propel




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                        8
Symfony 1.3 + Doctrine 1.2




          New Symfony Features




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   9
Symfony 1.3 + Doctrine 1.2




      Use the --installer option to
       execute Symfony installer
                 scripts


Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   10
Symfony 1.3 + Doctrine 1.2




      $ php /path/to/symfony generate:project --installer=my_installer.php




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                                 11
Symfony 1.3 + Doctrine 1.2
 Now in your my_installer.php you can do things like...
   if (!$this->askConfirmation('Are you sure you want to run this installer?'))
   {
     return;
   }

   $this->installDir(dirname(__FILE__).'/skeleton');

   $this->runTask('plugin:publish-assets');

   $validator = new sfValidatorEmail(array(), array('invalid' => 'hmmm, it does not look like
   an email!'));
   $email = $this->askAndValidate('Please, give me your email:', $validator);

   $this->runTask('configure:author', sprintf("'%s'", $email));

   $secret = $this->ask('Give a unique string for the CSRF secret:');

   $this->runTask('generate:app', 'frontend --escaping-strategy=true --csrf-secret='.
   $secret);

   $this->runTask('plugin:install', 'sfDoctrineGuardPlugin');
   $this->reloadTasks();

   $this->runTask('guard:create-user', 'jwage changeme');
   $this->runTask('cache:clear');

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                                                12
Symfony 1.3 + Doctrine 1.2




     It replaces the need to
   maintain your own skeleton
   symfony project. Just write
   your own custom installer to
 generate a new project the way
           you want it.

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   13
Symfony 1.3 + Doctrine 1.2




                 Form Framework




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   14
Symfony 1.3 + Doctrine 1.2


 Recognize this?
               class LoginForm extends BaseUserForm
               {
                 public function configure()
                 {
                   unset(
                      $this['first_name'],
                      $this['last_name'],
                      $this['email_address']
                   );
                 }
               }




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2          15
Symfony 1.3 + Doctrine 1.2




   Using unset() to remove fields




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   16
Symfony 1.3 + Doctrine 1.2




     Instead, lets say what form
   fields we WANT to use instead
      of the ones we don’t want



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   17
Symfony 1.3 + Doctrine 1.2


 Now it is possible with the useFields() method.

       class LoginForm extends BaseUserForm
       {
         public function configure()
         {
           $this->useFields(array(
             'username', 'password'
           ));
         }
       }


Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2       18
Symfony 1.3 + Doctrine 1.2




                 Easier to maintain




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   19
Symfony 1.3 + Doctrine 1.2




      Does not require keeping
    unset() list updated when we
      add new fields to model


Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   20
Symfony 1.3 + Doctrine 1.2




                    Forms + Events




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   21
Symfony 1.3 + Doctrine 1.2




  We now have a sfFormSymfony
  in core and BaseForm class in
          each project.


Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   22
Symfony 1.3 + Doctrine 1.2




      Sits on top of the agnostic
     sfForm and couples symfony
       dispatcher with the form
              framework.

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   23
Symfony 1.3 + Doctrine 1.2




   Allows us to dispatch symfony
    events from within the form
         framework. YES!



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   24
Symfony 1.3 + Doctrine 1.2




            What events can I use?




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   25
Symfony 1.3 + Doctrine 1.2



• form.post_configure - Notified after every form is
  configured

• form.filter_values - Filters tainted parameters and files
  array just prior to binding

• form.validation_error - Notified whenever form validation
  fails.

• form.method_not_found - Notified whenever an unknown
  method is called


Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                  26
Symfony 1.3 + Doctrine 1.2




             Example Form Event

    Add easy ReCaptcha functionality to
                all forms



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   27
Symfony 1.3 + Doctrine 1.2


 Define a regular form that extends sfFormSymfony


  class MyForm extends BaseForm
  {
    public function configure()
    {
      $this->widgetSchema['title'] = new sfWidgetFormInputText();
      $this->validatorSchema['title'] = new sfValidatorString();

          $this->widgetSchema->setNameFormat('my_form[%s]');
      }
  }




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                    28
Symfony 1.3 + Doctrine 1.2


 Connect events form.post_configure and form.filter_values
        class ProjectConfiguration extends sfProjectConfiguration
        {
          public function setup()
          {
            // ...

                $this->dispatcher->connect('form.post_configure', array(
                  $this, 'formPostConfigure'
                ));

                $this->dispatcher->connect('form.filter_values', array(
                  $this, 'formFilterValues'
                ));
            }
        }




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                               29
Symfony 1.3 + Doctrine 1.2


 Define formPostConfigure() method.
         public function formPostConfigure(sfEvent $event)
         {
           $form = $event->getSubject();

             if ($form->reCaptcha)
             {
               $widgetSchema = $form->getWidgetSchema();
               $validatorSchema = $form->getValidatorSchema();
               $widgetSchema['captcha'] = new sfWidgetFormReCaptcha(array(
                 'public_key' => '6Ld2DgQAAAAAAApXLteupHPcbSxbSHkhNTuYLChX'
               ));

                 $validatorSchema['captcha'] = new sfValidatorReCaptcha(array(
                   'private_key' => '6Ld2DgQAAAAAANIbaXJsFEBOyg56CL_ljy3APlPb'
                 ));
             }
         }




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                                     30
Symfony 1.3 + Doctrine 1.2


 Define formFilterValues() method.
   public function formFilterValues(sfEvent $event, $values)
   {
     $form = $event->getSubject();

       if ($form->reCaptcha)
       {
         $request = sfContext::getInstance()->getRequest();

        $captcha = array(
           'captcha' => array(
             'recaptcha_challenge_field' => $request->getParameter('recaptcha_challenge_field'),
             'recaptcha_response_field' => $request->getParameter('recaptcha_response_field')
           )
        );
        $values = array_merge($values, $captcha);
       }
       return $values;
   }




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                                                 31
Symfony 1.3 + Doctrine 1.2




    Now to enable recaptcha for
     any of your forms just add a
    public property to your forms
         with a value of true


Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   32
Symfony 1.3 + Doctrine 1.2


 Enable recaptcha on MyForm.

 class MyForm extends BaseForm
 {
   public $reCaptcha = true;

     public function configure()
     {
       $this->widgetSchema['title'] = new sfWidgetFormInputText();
       $this->validatorSchema['title'] = new sfValidatorString();

         $this->widgetSchema->setNameFormat('my_form[%s]');
     }
 }




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                     33
Symfony 1.3 + Doctrine 1.2




     Now MyForm has recaptcha
     enabled. Enable it on any of
          your other forms.



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   34
Symfony 1.3 + Doctrine 1.2




  The previous example requires
    that sfFormExtraPlugin be
     installed in your project.



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   35
Symfony 1.3 + Doctrine 1.2




      Of course we could have
     implemented this right in
  BaseForm without events. This
    would make more sense if
    implemented via a plugin.

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   36
Symfony 1.3 + Doctrine 1.2




           Testing Improvements




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   37
Symfony 1.3 + Doctrine 1.2




            sfTesterResponse adds
              matches() method



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   38
Symfony 1.3 + Doctrine 1.2




       Runs a regex on the entire
           response content.



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   39
Symfony 1.3 + Doctrine 1.2




        $browser->with('response')->begin()->
            matches('/I // it takes d+ apples/')->
$browser->with('response')->begin()->
  matches('/I have d+ apples/')->    have a regex as an argument
  matches('!/I have d+ apples/')->
            matches('!/I// havealso add regex modifiers
                                      // a ! at the beginning means that the regex must not match
  matches('!/I have d+ apples/i')->     you can   d+ apples/')->
end();
            matches('!/I have d+ apples/i')->
        end();




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                                                40
Symfony 1.3 + Doctrine 1.2




  More powerful than contains()
  and is useful for non-XML type
         responses where
 checkElement() cannot be used


Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   41
Symfony 1.3 + Doctrine 1.2




            Tests now have JUnit
           Compatible XML Output

         $ php symfony test:all --xml=log.xml




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2    42
Symfony 1.3 + Doctrine 1.2




   Output exception traces when
         running all tests

         $ php symfony test:all -t



                       Useful for debugging failing tests



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                43
Symfony 1.3 + Doctrine 1.2




     New checkForm() method on
          sfTesterResponse
            Tests that all form fields are rendered
            properly in the HTML response.



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2          44
Symfony 1.3 + Doctrine 1.2




      $browser->with('response')->begin()->
        checkForm('ArticleForm')->
      end();




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   45
Symfony 1.3 + Doctrine 1.2


 Use CSS selectors when dealing with multiple forms




 $browser->with('response')->begin()->
   checkForm('ArticleForm', '#articleForm')->
 end();




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2          46
Symfony 1.3 + Doctrine 1.2




       Improve Developer Testing
              Efficiency



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   47
Symfony 1.3 + Doctrine 1.2




      When you have a large test
     suite it is time consuming to
     run all tests every time you
            make a change.


Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   48
Symfony 1.3 + Doctrine 1.2




      Use the --only-failed option




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   49
Symfony 1.3 + Doctrine 1.2




    After you run the test suite
       once, re-run with the
  --only-failed option to execute
   only the failed tests from the
           previous run.

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   50
Symfony 1.3 + Doctrine 1.2




    Now you can re-run this over
    and over until you have zero
          failures again.


Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   51
Symfony 1.3 + Doctrine 1.2




             Interactive CLI Tasks

  Ask the user for some input and use it
               in your task



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   52
Symfony 1.3 + Doctrine 1.2


 In your task class you can use some code like below.




          $anwser = $this->askAndValidate(
             'What is you email?',
             new sfValidatorEmail()
          );




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2            53
Symfony 1.3 + Doctrine 1.2




 Improved Doctrine Integration




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   54
Symfony 1.3 + Doctrine 1.2




     Disable Form Generation for
            Certain Models



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   55
Symfony 1.3 + Doctrine 1.2
   For models like many to many join tables we don’t need
    form classes so we can disable the generation of them.

                          UserGroup:
                            options:
                              symfony:
                                form: false
                                filter: false
                            columns:
                              user_id:
                                type: integer
                                primary: true
                              group_id:
                                type: integer
                                primary: true



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2             56
Symfony 1.3 + Doctrine 1.2




     Generated forms now follow
     model inheritance hierarchy



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   57
Symfony 1.3 + Doctrine 1.2


      Now your form classes will generate the same
     inheritance structure that your models define.

               class Moderator extends User
               {
                 // ...
               }

               class ModeratorForm extends UserForm
               {
                 // ...
               }




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2          58
Symfony 1.3 + Doctrine 1.2




              New Doctrine Tasks




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   59
Symfony 1.3 + Doctrine 1.2




Create Tables for Set of Models
           Task will drop and recreate specified tables.

      $ php symfony doctrine:create-model-tables Model1 Model2 Model3


       Useful in development mode when you want to
       rebuild a set of models in the database over and
       over while you’re working on it. Instead of having
       to rebuild the entire database just to test.

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                            60
Symfony 1.3 + Doctrine 1.2




                Delete Model Files
               Delete associated generated files for
               a model like forms and filter classes.

            $ php symfony doctrine:delete-model-files ModelName



     This is useful if you remove a model or rename a
     model, before you would have to manually clean up
     these classes and it was a pain. This task automates
     it for you.

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                      61
Symfony 1.3 + Doctrine 1.2




                 Clean Model Files
             This task is combined with the previous
             task to clean up orphaned model files.

                   $ php symfony doctrine:clean-model-files


     It detects what model all model files exist on disk but
     cannot be found in your YAML schema files. It will
     find these files and report them to you asking you to
     confirm whether or not they should be deleted.

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                   62
Symfony 1.3 + Doctrine 1.2



                       Reload Data
                     $ php symfony doctrine:reload-data


          The above is the same as doing the following

                     $ php symfony doctrine:drop-db
                     $ php symfony doctrine:build-db
                     $ php symfony doctrine:insert-sql
                     $ php symfony doctrine:data-load


              This is commonly used to prepare a
              test database before each test is run.

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2              63
Symfony 1.3 + Doctrine 1.2




              The New Build Task
  The new doctrine:build task allows you to specify what
  exactly you would like symfony and Doctrine to build. This
  task replicates the functionality in many of the existing
  combination-tasks, which have all been deprecated in
  favor of this more flexible solution.



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2              64
Symfony 1.3 + Doctrine 1.2



                    Build Examples
                  $ php symfony doctrine:build --db --and-load



            This will drop and create the database,
            create the tables configured in your
            YAML schema and load the fixture data.




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                     65
Symfony 1.3 + Doctrine 1.2



                    Build Examples
            $ php symfony doctrine:build --all-classes --and-migrate




              This will build the model, forms, form
              filters and run any pending migrations.




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                           66
Symfony 1.3 + Doctrine 1.2



                    Build Examples
$ php symfony doctrine:build --model --and-migrate --and-append=data/fixtures/
categories.yml




          This will build the model, migrate the
          database and append category fixtures data.




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                               67
Symfony 1.3 + Doctrine 1.2



    Dealing with Doctrine Dates

  We've added two new methods for retrieving Doctrine
  date/time values as PHP DateTime object instances.




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2            68
Symfony 1.3 + Doctrine 1.2



     If you prefer to work with DateTime objects then you can
      use these methods to get and set your Doctrine date
      values.




    echo $article->getDateTimeObject('created_at')->format('m/d/Y');

    $article->setDateTimeObject('created_at', new DateTime('09/01/1985'));




                www.php.net/DateTime

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                            69
Symfony 1.3 + Doctrine 1.2


Alternative doctrine:dql output
  $ ./symfony doctrine:dql "FROM Article a" --table
  >> doctrine executing dql query
  DQL: FROM Article a
  +----+-----------+----------------+---------------------+---------------------+
  | id | author_id | is_on_homepage | created_at          | updated_at          |
  +----+-----------+----------------+---------------------+---------------------+
  | 1 | 1          |                | 2009-07-07 18:02:24 | 2009-07-07 18:02:24 |
  | 2 | 2          |                | 2009-07-07 18:02:24 | 2009-07-07 18:02:24 |
  +----+-----------+----------------+---------------------+---------------------+
  (2 results)




          You probably recognize this as it is how the
          mysql command line outputs SQL queries.

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                                   70
Symfony 1.3 + Doctrine 1.2




                       Doctrine 1.2




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   71
Symfony 1.3 + Doctrine 1.2




         Easy to upgrade from 1.1




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   72
Symfony 1.3 + Doctrine 1.2




   Some deprecated functions,
  methods, etc. removed but the
   core of things have remained
    mostly the same. Only new
  features and flexibilities have
            been added.

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   73
Symfony 1.3 + Doctrine 1.2




               Custom Query Class




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   74
Symfony 1.3 + Doctrine 1.2


 Specify the query class to use in your project somewhere
    in your configuration.




      $manager->setAttribute(
         Doctrine::ATTR_QUERY_CLASS,
         'MyQuery'
      );




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                75
Symfony 1.3 + Doctrine 1.2


 Define the MyQuery class which extends Doctrine_Query




       class MyQuery extends Doctrine_Query
       {

       }




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2             76
Symfony 1.3 + Doctrine 1.2




 Now you can add new methods
   and override functionality in
 the Doctrine_Query class for all
    your query objects in your
           application

Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   77
Symfony 1.3 + Doctrine 1.2




          Custom Collection Class




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   78
Symfony 1.3 + Doctrine 1.2


 Specify the collection class to use in your project
    somewhere in your configuration.



  $manager->setAttribute(
     Doctrine::ATTR_COLLECTION_CLASS,
     'MyCollection'
  );




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2           79
Symfony 1.3 + Doctrine 1.2


 Define the MyCollection class which extends
    Doctrine_Collection




  class MyCollection extends Doctrine_Collection
  {

  }




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   80
Symfony 1.3 + Doctrine 1.2




  Now you can add new methods
   and override functionality in
        your collections.



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   81
Symfony 1.3 + Doctrine 1.2


 Add new method to get the sum of a field in a collection.


            class MyCollection extends Doctrine_Collection
            {
              public function getSum($field)
              {
                $sum = 0;
                foreach ($this as $record)
                {
                  $sum = $sum + $record->get($field);
                }
                return $sum;
              }
            }




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                 82
Symfony 1.3 + Doctrine 1.2


 Now it can be used like the following




  $transactions = Doctrine::getTable('Transaction')
    ->retrieveAllUserTransactions('jwage');

  echo get_class($transactions); // MyCollection

  // Output the total amount billed for all transactions for jwage
  echo $transactions->getSum('total_billed');




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                    83
Symfony 1.3 + Doctrine 1.2




           Custom Data Hydrators




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   84
Symfony 1.3 + Doctrine 1.2




 It is now possible to write and register your own custom
 data hydrators which can be used from your query objects.



   $manager->registerHydrator('MyHydrator', ‘MyHydratorDriver’);




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                       85
Symfony 1.3 + Doctrine 1.2


 Define the MyHydrator class



  class MyHydratorDriver extends Doctrine_Hydrator_Abstract
  {
      public function hydrateResultSet($stmt)
      {
          return $stmt->fetchAll(PDO::FETCH_ASSOC);
      }
  }




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2             86
Symfony 1.3 + Doctrine 1.2




  Custom hydrators must extend
   Doctrine_Hydrator_Abstract
     and must implement the
   hydrateResultSet() method.


Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   87
Symfony 1.3 + Doctrine 1.2



 The core hydrators execute the
    PDO statement and build a
      special structure to be
  returned. You can execute the
 statement and do whatever you
    want with the data before
           returning it.
Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   88
Symfony 1.3 + Doctrine 1.2



           Using Custom Hydrator


        $q->execute(array(), 'MyHydrator');




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   89
Symfony 1.3 + Doctrine 1.2




       Custom Connection Classes




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   90
Symfony 1.3 + Doctrine 1.2




     Sometimes you may have some
 proprietary database type that Doctrine
      does not support. The custom
   connections allow you to implement
    your own database drivers to use.



Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   91
Symfony 1.3 + Doctrine 1.2



       Register Connection Driver

   $manager->registerConnectionDriver(
     'test', 'Doctrine_Connection_Test'
   );




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   92
Symfony 1.3 + Doctrine 1.2



        Define Connection Classes
 class Doctrine_Connection_Test extends Doctrine_Connection_Common
 {
 }

 class Doctrine_Adapter_Test implements Doctrine_Adapter_Interface
 {
   // ... all the methods defined in the interface
   // ... mimics the PDO interface which connects
   // ... to your database
 }




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                    93
Symfony 1.3 + Doctrine 1.2



           Open a New Connection
  $conn = $manager->openConnection('test://username:password@localhost/dbname');

  echo get_class($conn); // Doctrine_Connection_Test
  echo get_class($conn->getDbh()); // Doctrine_Adapter_Test




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                                  94
Symfony 1.3 + Doctrine 1.2




       Improve Save Performance
$manager->setAttribute(Doctrine::ATTR_CASCADE_SAVES, false);


     Disabling cascading saves to improve performance
     when saving lots of objects. The negative affect of
     this change is that you can’t save dirty objects that
     are more than one level deep in the object hierarchy.


Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                 95
Symfony 1.3 + Doctrine 1.2




 New Convenience Magic Finders




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   96
Symfony 1.3 + Doctrine 1.2


 Method name is converted in to
 a DQL query. Combine AND/OR
          conditions.
$user = $table->findOneByUsernameAndPassword('jwage', md5('changeme'));
$users = $table->findByIsAdminOrIsModerator(true, true);




   Please be aware these finders are very limited and are
   only meant for quickly prototyping code. It is almost
   always recommended to write your own DQL queries
   which select and join the exact resultset you need.
Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                              97
Symfony 1.3 + Doctrine 1.2




Doctrine Extensions Repository
     With the start of Doctrine 1.2 development we
     opened a special repository for users to contribute
     extensions to Doctrine to share with the community.




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2               98
Symfony 1.3 + Doctrine 1.2



       Similar to Symfony Plugins




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   99
Symfony 1.3 + Doctrine 1.2




             Using the Extensions
   Outside of Symfony Doctrine has a mechanism for
   autoloading and registering extensions. This is not
   necessary in Symfony because the native autoloading
   will load the code for the extension. We just need to
   download the code and put it somewhere in Symfony.


Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2               100
Symfony 1.3 + Doctrine 1.2



              Using the Extensions
$ cd /path/to/symfony/project
$ mkdir lib/doctrine_extensions
$ svn co http://svn.doctrine-project.org/extensions/Taggable/branches/1.2-1.0/
Taggable
$ ./symfony cc




Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                                101
Symfony 1.3 + Doctrine 1.2


             Using the Extensions
          ---
          Article:
            actAs: [Taggable]
            columns:
              title: string(255)
              body: clob
Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2   102
Symfony 1.3 + Doctrine 1.2

                          Questions?
        Jonathan H. Wage
        jonathan.wage@sensio.com
        +1 415 992 5468

        sensiolabs.com | doctrine-project.org | sympalphp.org | jwage.com



You can contact Jonathan about Doctrine and Open-Source or
 for training, consulting, application development, or business
        related questions at jonathan.wage@sensio.com


Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2                                103

Symfony 1.3 + Doctrine 1.2

  • 1.
    Symfony 1.3 +Doctrine 1.2 Symfony 1.3 + Doctrine 1.2 Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 1
  • 2.
    Symfony 1.3 +Doctrine 1.2 Continued Evolution of Symfony 1.x Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 2
  • 3.
    Symfony 1.3 +Doctrine 1.2 Lots of new features, enhancements and bug fixes. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 3
  • 4.
    Symfony 1.3 +Doctrine 1.2 Out with the old and in with the new! Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 4
  • 5.
    Symfony 1.3 +Doctrine 1.2 Doctrine is the default ORM Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 5
  • 6.
    Symfony 1.3 +Doctrine 1.2 Propel is deprecated Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 6
  • 7.
    Symfony 1.3 +Doctrine 1.2 Propel is deprecated in 1.3 but not removed. It will be dropped completely in symfony 2.0 Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 7
  • 8.
    Symfony 1.3 +Doctrine 1.2 Still want to use Propel? :) Generate your project with the new --orm option $ php /path/to/symfony generate:project foo --orm=Propel Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 8
  • 9.
    Symfony 1.3 +Doctrine 1.2 New Symfony Features Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 9
  • 10.
    Symfony 1.3 +Doctrine 1.2 Use the --installer option to execute Symfony installer scripts Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 10
  • 11.
    Symfony 1.3 +Doctrine 1.2 $ php /path/to/symfony generate:project --installer=my_installer.php Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 11
  • 12.
    Symfony 1.3 +Doctrine 1.2 Now in your my_installer.php you can do things like... if (!$this->askConfirmation('Are you sure you want to run this installer?')) { return; } $this->installDir(dirname(__FILE__).'/skeleton'); $this->runTask('plugin:publish-assets'); $validator = new sfValidatorEmail(array(), array('invalid' => 'hmmm, it does not look like an email!')); $email = $this->askAndValidate('Please, give me your email:', $validator); $this->runTask('configure:author', sprintf("'%s'", $email)); $secret = $this->ask('Give a unique string for the CSRF secret:'); $this->runTask('generate:app', 'frontend --escaping-strategy=true --csrf-secret='. $secret); $this->runTask('plugin:install', 'sfDoctrineGuardPlugin'); $this->reloadTasks(); $this->runTask('guard:create-user', 'jwage changeme'); $this->runTask('cache:clear'); Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 12
  • 13.
    Symfony 1.3 +Doctrine 1.2 It replaces the need to maintain your own skeleton symfony project. Just write your own custom installer to generate a new project the way you want it. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 13
  • 14.
    Symfony 1.3 +Doctrine 1.2 Form Framework Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 14
  • 15.
    Symfony 1.3 +Doctrine 1.2 Recognize this? class LoginForm extends BaseUserForm { public function configure() { unset( $this['first_name'], $this['last_name'], $this['email_address'] ); } } Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 15
  • 16.
    Symfony 1.3 +Doctrine 1.2 Using unset() to remove fields Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 16
  • 17.
    Symfony 1.3 +Doctrine 1.2 Instead, lets say what form fields we WANT to use instead of the ones we don’t want Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 17
  • 18.
    Symfony 1.3 +Doctrine 1.2 Now it is possible with the useFields() method. class LoginForm extends BaseUserForm { public function configure() { $this->useFields(array( 'username', 'password' )); } } Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 18
  • 19.
    Symfony 1.3 +Doctrine 1.2 Easier to maintain Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 19
  • 20.
    Symfony 1.3 +Doctrine 1.2 Does not require keeping unset() list updated when we add new fields to model Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 20
  • 21.
    Symfony 1.3 +Doctrine 1.2 Forms + Events Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 21
  • 22.
    Symfony 1.3 +Doctrine 1.2 We now have a sfFormSymfony in core and BaseForm class in each project. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 22
  • 23.
    Symfony 1.3 +Doctrine 1.2 Sits on top of the agnostic sfForm and couples symfony dispatcher with the form framework. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 23
  • 24.
    Symfony 1.3 +Doctrine 1.2 Allows us to dispatch symfony events from within the form framework. YES! Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 24
  • 25.
    Symfony 1.3 +Doctrine 1.2 What events can I use? Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 25
  • 26.
    Symfony 1.3 +Doctrine 1.2 • form.post_configure - Notified after every form is configured • form.filter_values - Filters tainted parameters and files array just prior to binding • form.validation_error - Notified whenever form validation fails. • form.method_not_found - Notified whenever an unknown method is called Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 26
  • 27.
    Symfony 1.3 +Doctrine 1.2 Example Form Event Add easy ReCaptcha functionality to all forms Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 27
  • 28.
    Symfony 1.3 +Doctrine 1.2 Define a regular form that extends sfFormSymfony class MyForm extends BaseForm { public function configure() { $this->widgetSchema['title'] = new sfWidgetFormInputText(); $this->validatorSchema['title'] = new sfValidatorString(); $this->widgetSchema->setNameFormat('my_form[%s]'); } } Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 28
  • 29.
    Symfony 1.3 +Doctrine 1.2 Connect events form.post_configure and form.filter_values class ProjectConfiguration extends sfProjectConfiguration { public function setup() { // ... $this->dispatcher->connect('form.post_configure', array( $this, 'formPostConfigure' )); $this->dispatcher->connect('form.filter_values', array( $this, 'formFilterValues' )); } } Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 29
  • 30.
    Symfony 1.3 +Doctrine 1.2 Define formPostConfigure() method. public function formPostConfigure(sfEvent $event) { $form = $event->getSubject(); if ($form->reCaptcha) { $widgetSchema = $form->getWidgetSchema(); $validatorSchema = $form->getValidatorSchema(); $widgetSchema['captcha'] = new sfWidgetFormReCaptcha(array( 'public_key' => '6Ld2DgQAAAAAAApXLteupHPcbSxbSHkhNTuYLChX' )); $validatorSchema['captcha'] = new sfValidatorReCaptcha(array( 'private_key' => '6Ld2DgQAAAAAANIbaXJsFEBOyg56CL_ljy3APlPb' )); } } Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 30
  • 31.
    Symfony 1.3 +Doctrine 1.2 Define formFilterValues() method. public function formFilterValues(sfEvent $event, $values) { $form = $event->getSubject(); if ($form->reCaptcha) { $request = sfContext::getInstance()->getRequest(); $captcha = array( 'captcha' => array( 'recaptcha_challenge_field' => $request->getParameter('recaptcha_challenge_field'), 'recaptcha_response_field' => $request->getParameter('recaptcha_response_field') ) ); $values = array_merge($values, $captcha); } return $values; } Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 31
  • 32.
    Symfony 1.3 +Doctrine 1.2 Now to enable recaptcha for any of your forms just add a public property to your forms with a value of true Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 32
  • 33.
    Symfony 1.3 +Doctrine 1.2 Enable recaptcha on MyForm. class MyForm extends BaseForm { public $reCaptcha = true; public function configure() { $this->widgetSchema['title'] = new sfWidgetFormInputText(); $this->validatorSchema['title'] = new sfValidatorString(); $this->widgetSchema->setNameFormat('my_form[%s]'); } } Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 33
  • 34.
    Symfony 1.3 +Doctrine 1.2 Now MyForm has recaptcha enabled. Enable it on any of your other forms. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 34
  • 35.
    Symfony 1.3 +Doctrine 1.2 The previous example requires that sfFormExtraPlugin be installed in your project. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 35
  • 36.
    Symfony 1.3 +Doctrine 1.2 Of course we could have implemented this right in BaseForm without events. This would make more sense if implemented via a plugin. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 36
  • 37.
    Symfony 1.3 +Doctrine 1.2 Testing Improvements Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 37
  • 38.
    Symfony 1.3 +Doctrine 1.2 sfTesterResponse adds matches() method Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 38
  • 39.
    Symfony 1.3 +Doctrine 1.2 Runs a regex on the entire response content. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 39
  • 40.
    Symfony 1.3 +Doctrine 1.2 $browser->with('response')->begin()-> matches('/I // it takes d+ apples/')-> $browser->with('response')->begin()-> matches('/I have d+ apples/')-> have a regex as an argument matches('!/I have d+ apples/')-> matches('!/I// havealso add regex modifiers // a ! at the beginning means that the regex must not match matches('!/I have d+ apples/i')-> you can d+ apples/')-> end(); matches('!/I have d+ apples/i')-> end(); Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 40
  • 41.
    Symfony 1.3 +Doctrine 1.2 More powerful than contains() and is useful for non-XML type responses where checkElement() cannot be used Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 41
  • 42.
    Symfony 1.3 +Doctrine 1.2 Tests now have JUnit Compatible XML Output $ php symfony test:all --xml=log.xml Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 42
  • 43.
    Symfony 1.3 +Doctrine 1.2 Output exception traces when running all tests $ php symfony test:all -t Useful for debugging failing tests Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 43
  • 44.
    Symfony 1.3 +Doctrine 1.2 New checkForm() method on sfTesterResponse Tests that all form fields are rendered properly in the HTML response. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 44
  • 45.
    Symfony 1.3 +Doctrine 1.2 $browser->with('response')->begin()-> checkForm('ArticleForm')-> end(); Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 45
  • 46.
    Symfony 1.3 +Doctrine 1.2 Use CSS selectors when dealing with multiple forms $browser->with('response')->begin()-> checkForm('ArticleForm', '#articleForm')-> end(); Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 46
  • 47.
    Symfony 1.3 +Doctrine 1.2 Improve Developer Testing Efficiency Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 47
  • 48.
    Symfony 1.3 +Doctrine 1.2 When you have a large test suite it is time consuming to run all tests every time you make a change. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 48
  • 49.
    Symfony 1.3 +Doctrine 1.2 Use the --only-failed option Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 49
  • 50.
    Symfony 1.3 +Doctrine 1.2 After you run the test suite once, re-run with the --only-failed option to execute only the failed tests from the previous run. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 50
  • 51.
    Symfony 1.3 +Doctrine 1.2 Now you can re-run this over and over until you have zero failures again. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 51
  • 52.
    Symfony 1.3 +Doctrine 1.2 Interactive CLI Tasks Ask the user for some input and use it in your task Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 52
  • 53.
    Symfony 1.3 +Doctrine 1.2 In your task class you can use some code like below. $anwser = $this->askAndValidate( 'What is you email?', new sfValidatorEmail() ); Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 53
  • 54.
    Symfony 1.3 +Doctrine 1.2 Improved Doctrine Integration Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 54
  • 55.
    Symfony 1.3 +Doctrine 1.2 Disable Form Generation for Certain Models Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 55
  • 56.
    Symfony 1.3 +Doctrine 1.2 For models like many to many join tables we don’t need form classes so we can disable the generation of them. UserGroup: options: symfony: form: false filter: false columns: user_id: type: integer primary: true group_id: type: integer primary: true Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 56
  • 57.
    Symfony 1.3 +Doctrine 1.2 Generated forms now follow model inheritance hierarchy Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 57
  • 58.
    Symfony 1.3 +Doctrine 1.2 Now your form classes will generate the same inheritance structure that your models define. class Moderator extends User { // ... } class ModeratorForm extends UserForm { // ... } Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 58
  • 59.
    Symfony 1.3 +Doctrine 1.2 New Doctrine Tasks Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 59
  • 60.
    Symfony 1.3 +Doctrine 1.2 Create Tables for Set of Models Task will drop and recreate specified tables. $ php symfony doctrine:create-model-tables Model1 Model2 Model3 Useful in development mode when you want to rebuild a set of models in the database over and over while you’re working on it. Instead of having to rebuild the entire database just to test. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 60
  • 61.
    Symfony 1.3 +Doctrine 1.2 Delete Model Files Delete associated generated files for a model like forms and filter classes. $ php symfony doctrine:delete-model-files ModelName This is useful if you remove a model or rename a model, before you would have to manually clean up these classes and it was a pain. This task automates it for you. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 61
  • 62.
    Symfony 1.3 +Doctrine 1.2 Clean Model Files This task is combined with the previous task to clean up orphaned model files. $ php symfony doctrine:clean-model-files It detects what model all model files exist on disk but cannot be found in your YAML schema files. It will find these files and report them to you asking you to confirm whether or not they should be deleted. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 62
  • 63.
    Symfony 1.3 +Doctrine 1.2 Reload Data $ php symfony doctrine:reload-data The above is the same as doing the following $ php symfony doctrine:drop-db $ php symfony doctrine:build-db $ php symfony doctrine:insert-sql $ php symfony doctrine:data-load This is commonly used to prepare a test database before each test is run. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 63
  • 64.
    Symfony 1.3 +Doctrine 1.2 The New Build Task The new doctrine:build task allows you to specify what exactly you would like symfony and Doctrine to build. This task replicates the functionality in many of the existing combination-tasks, which have all been deprecated in favor of this more flexible solution. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 64
  • 65.
    Symfony 1.3 +Doctrine 1.2 Build Examples $ php symfony doctrine:build --db --and-load This will drop and create the database, create the tables configured in your YAML schema and load the fixture data. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 65
  • 66.
    Symfony 1.3 +Doctrine 1.2 Build Examples $ php symfony doctrine:build --all-classes --and-migrate This will build the model, forms, form filters and run any pending migrations. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 66
  • 67.
    Symfony 1.3 +Doctrine 1.2 Build Examples $ php symfony doctrine:build --model --and-migrate --and-append=data/fixtures/ categories.yml This will build the model, migrate the database and append category fixtures data. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 67
  • 68.
    Symfony 1.3 +Doctrine 1.2 Dealing with Doctrine Dates We've added two new methods for retrieving Doctrine date/time values as PHP DateTime object instances. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 68
  • 69.
    Symfony 1.3 +Doctrine 1.2 If you prefer to work with DateTime objects then you can use these methods to get and set your Doctrine date values. echo $article->getDateTimeObject('created_at')->format('m/d/Y'); $article->setDateTimeObject('created_at', new DateTime('09/01/1985')); www.php.net/DateTime Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 69
  • 70.
    Symfony 1.3 +Doctrine 1.2 Alternative doctrine:dql output $ ./symfony doctrine:dql "FROM Article a" --table >> doctrine executing dql query DQL: FROM Article a +----+-----------+----------------+---------------------+---------------------+ | id | author_id | is_on_homepage | created_at | updated_at | +----+-----------+----------------+---------------------+---------------------+ | 1 | 1 | | 2009-07-07 18:02:24 | 2009-07-07 18:02:24 | | 2 | 2 | | 2009-07-07 18:02:24 | 2009-07-07 18:02:24 | +----+-----------+----------------+---------------------+---------------------+ (2 results) You probably recognize this as it is how the mysql command line outputs SQL queries. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 70
  • 71.
    Symfony 1.3 +Doctrine 1.2 Doctrine 1.2 Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 71
  • 72.
    Symfony 1.3 +Doctrine 1.2 Easy to upgrade from 1.1 Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 72
  • 73.
    Symfony 1.3 +Doctrine 1.2 Some deprecated functions, methods, etc. removed but the core of things have remained mostly the same. Only new features and flexibilities have been added. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 73
  • 74.
    Symfony 1.3 +Doctrine 1.2 Custom Query Class Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 74
  • 75.
    Symfony 1.3 +Doctrine 1.2 Specify the query class to use in your project somewhere in your configuration. $manager->setAttribute( Doctrine::ATTR_QUERY_CLASS, 'MyQuery' ); Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 75
  • 76.
    Symfony 1.3 +Doctrine 1.2 Define the MyQuery class which extends Doctrine_Query class MyQuery extends Doctrine_Query { } Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 76
  • 77.
    Symfony 1.3 +Doctrine 1.2 Now you can add new methods and override functionality in the Doctrine_Query class for all your query objects in your application Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 77
  • 78.
    Symfony 1.3 +Doctrine 1.2 Custom Collection Class Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 78
  • 79.
    Symfony 1.3 +Doctrine 1.2 Specify the collection class to use in your project somewhere in your configuration. $manager->setAttribute( Doctrine::ATTR_COLLECTION_CLASS, 'MyCollection' ); Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 79
  • 80.
    Symfony 1.3 +Doctrine 1.2 Define the MyCollection class which extends Doctrine_Collection class MyCollection extends Doctrine_Collection { } Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 80
  • 81.
    Symfony 1.3 +Doctrine 1.2 Now you can add new methods and override functionality in your collections. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 81
  • 82.
    Symfony 1.3 +Doctrine 1.2 Add new method to get the sum of a field in a collection. class MyCollection extends Doctrine_Collection { public function getSum($field) { $sum = 0; foreach ($this as $record) { $sum = $sum + $record->get($field); } return $sum; } } Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 82
  • 83.
    Symfony 1.3 +Doctrine 1.2 Now it can be used like the following $transactions = Doctrine::getTable('Transaction') ->retrieveAllUserTransactions('jwage'); echo get_class($transactions); // MyCollection // Output the total amount billed for all transactions for jwage echo $transactions->getSum('total_billed'); Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 83
  • 84.
    Symfony 1.3 +Doctrine 1.2 Custom Data Hydrators Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 84
  • 85.
    Symfony 1.3 +Doctrine 1.2 It is now possible to write and register your own custom data hydrators which can be used from your query objects. $manager->registerHydrator('MyHydrator', ‘MyHydratorDriver’); Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 85
  • 86.
    Symfony 1.3 +Doctrine 1.2 Define the MyHydrator class class MyHydratorDriver extends Doctrine_Hydrator_Abstract { public function hydrateResultSet($stmt) { return $stmt->fetchAll(PDO::FETCH_ASSOC); } } Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 86
  • 87.
    Symfony 1.3 +Doctrine 1.2 Custom hydrators must extend Doctrine_Hydrator_Abstract and must implement the hydrateResultSet() method. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 87
  • 88.
    Symfony 1.3 +Doctrine 1.2 The core hydrators execute the PDO statement and build a special structure to be returned. You can execute the statement and do whatever you want with the data before returning it. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 88
  • 89.
    Symfony 1.3 +Doctrine 1.2 Using Custom Hydrator $q->execute(array(), 'MyHydrator'); Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 89
  • 90.
    Symfony 1.3 +Doctrine 1.2 Custom Connection Classes Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 90
  • 91.
    Symfony 1.3 +Doctrine 1.2 Sometimes you may have some proprietary database type that Doctrine does not support. The custom connections allow you to implement your own database drivers to use. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 91
  • 92.
    Symfony 1.3 +Doctrine 1.2 Register Connection Driver $manager->registerConnectionDriver( 'test', 'Doctrine_Connection_Test' ); Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 92
  • 93.
    Symfony 1.3 +Doctrine 1.2 Define Connection Classes class Doctrine_Connection_Test extends Doctrine_Connection_Common { } class Doctrine_Adapter_Test implements Doctrine_Adapter_Interface { // ... all the methods defined in the interface // ... mimics the PDO interface which connects // ... to your database } Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 93
  • 94.
    Symfony 1.3 +Doctrine 1.2 Open a New Connection $conn = $manager->openConnection('test://username:password@localhost/dbname'); echo get_class($conn); // Doctrine_Connection_Test echo get_class($conn->getDbh()); // Doctrine_Adapter_Test Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 94
  • 95.
    Symfony 1.3 +Doctrine 1.2 Improve Save Performance $manager->setAttribute(Doctrine::ATTR_CASCADE_SAVES, false); Disabling cascading saves to improve performance when saving lots of objects. The negative affect of this change is that you can’t save dirty objects that are more than one level deep in the object hierarchy. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 95
  • 96.
    Symfony 1.3 +Doctrine 1.2 New Convenience Magic Finders Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 96
  • 97.
    Symfony 1.3 +Doctrine 1.2 Method name is converted in to a DQL query. Combine AND/OR conditions. $user = $table->findOneByUsernameAndPassword('jwage', md5('changeme')); $users = $table->findByIsAdminOrIsModerator(true, true); Please be aware these finders are very limited and are only meant for quickly prototyping code. It is almost always recommended to write your own DQL queries which select and join the exact resultset you need. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 97
  • 98.
    Symfony 1.3 +Doctrine 1.2 Doctrine Extensions Repository With the start of Doctrine 1.2 development we opened a special repository for users to contribute extensions to Doctrine to share with the community. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 98
  • 99.
    Symfony 1.3 +Doctrine 1.2 Similar to Symfony Plugins Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 99
  • 100.
    Symfony 1.3 +Doctrine 1.2 Using the Extensions Outside of Symfony Doctrine has a mechanism for autoloading and registering extensions. This is not necessary in Symfony because the native autoloading will load the code for the extension. We just need to download the code and put it somewhere in Symfony. Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 100
  • 101.
    Symfony 1.3 +Doctrine 1.2 Using the Extensions $ cd /path/to/symfony/project $ mkdir lib/doctrine_extensions $ svn co http://svn.doctrine-project.org/extensions/Taggable/branches/1.2-1.0/ Taggable $ ./symfony cc Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 101
  • 102.
    Symfony 1.3 +Doctrine 1.2 Using the Extensions --- Article: actAs: [Taggable] columns: title: string(255) body: clob Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 102
  • 103.
    Symfony 1.3 +Doctrine 1.2 Questions? Jonathan H. Wage jonathan.wage@sensio.com +1 415 992 5468 sensiolabs.com | doctrine-project.org | sympalphp.org | jwage.com You can contact Jonathan about Doctrine and Open-Source or for training, consulting, application development, or business related questions at jonathan.wage@sensio.com Jonathan H. Wage: Symfony 1.3 + Doctrine 1.2 103