James Morris - JMOZ

I pretend I know PHP and internet stuff. Currently contracting at TimeOut London.

    • 7
      12 Dec 2011

      Symfony2 FOSUserBundle Role entities

      • Edit
      • Delete
      • Tags
      • Autopost

      I’ve been working with FOSUserBundle Roles recently, adding database persistence using a simple Doctrine array mapping. We needed a better implementation where the Roles could be managed from the database and dynamically added and removed to a User through an admin interface. I found quite a few posts on stackoverflow and the Symfony2 google group asking how best to implement Role entities but very few answers or solutions, and no documentation on the FOSUserBundle github page.

      Symfony2’s security system is one of the most complex parts of the framework. Couple this with FOSUserBundle’s weird AOP implementation and you’ve basically got a big massive ball ache when it comes to figuring out what’s going on. After staring through a lot of code and not really getting anywhere, I figured I’d write some tests to cover the existing functionality then refactor the User and Role classes until everything went green.

      Unit test to cover existing functionality

      For reference here is the unit test I was working with.

      Creating the Role entity

      FOSUserBundle provides no Role entity. Symfony2 provides a Role object but some class members are private so we just implement the Role interface, hoping it will ensure the new Role implementation works correctly with the security system.

      It’s a simple object, I’m implementing a __toString() method so we can loop in the template over User::getRoles() and echo the $role.

      Creating the User, Role relationship

      This is the User class with the Role relationship mapped. I tried to implement the same Role functionality as FOSUserBundle. You are restricted to certain method parameters due to the type hinting in the parent class, e.g. setRoles() must take an array. I found type hinting and return type expectations in some of the symfony2 security layer code, such as:

      UsernamePasswordToken::__construct($user, $credentials, $providerKey, array $roles = array())

      Because of this, I mixed up an ArrayCollection and array implementation. You can see I also provided the (set|get)RolesCollection() methods to make things easier when working with doctrine.

      Modify the schema

      Check the changes to the database:

      $ php app/console --env=test doctrine:schema:update --em=user --dump-sql
      CREATE TABLE security_roles (id INT AUTO_INCREMENT NOT NULL, role VARCHAR(70) NOT NULL, UNIQUE INDEX UNIQ_5A82CD6D57698A6A (role), PRIMARY KEY(id)) ENGINE = InnoDB;
      CREATE TABLE security_users_roles (user_id INT NOT NULL, role_id INT NOT NULL, INDEX IDX_71E6DDEFA76ED395 (user_id), INDEX IDX_71E6DDEFD60322AC (role_id), PRIMARY KEY(user_id, role_id)) ENGINE = InnoDB;
      ALTER TABLE security_users_roles ADD CONSTRAINT FK_71E6DDEFA76ED395 FOREIGN KEY (user_id) REFERENCES security_users(id) ON DELETE CASCADE;
      ALTER TABLE security_users_roles ADD CONSTRAINT FK_71E6DDEFD60322AC FOREIGN KEY (role_id) REFERENCES security_roles(id) ON DELETE CASCADE;
      ALTER TABLE security_users DROP roles

      Either run this using the --force parameter or create a migration.

      Issues

      This will break the FOSUserBundle promote and demote commands as they are hard coded to use addRole(string).

      Summary

      I managed to get all the tests to pass:

      $ phpunit --stop-on-error --stop-on-failure -c app src/JMOZ/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php
      PHPUnit 3.5.5 by Sebastian Bergmann.
      
      ...........
      
      Time: 30 seconds, Memory: 82.25Mb
      
      OK (11 tests, 35 assertions)

      I would rather have not mixed the array/ArrayCollection implementation but was forced to do so due to the existing functionality and interfaces. I’d have liked to have seen some other implementations or solutions but could not seem to find any. If anyone has seen anything better please let me know. I hope this helps someone out there!

      • views
      • Tweet
    • 0
      30 Nov 2011

      Symfony2 Warning: session_start(): The session id is too long or contains illegal characters...

      • Edit
      • Delete
      • Tags
      • Autopost

      Whilst trying to set up a new test environment I’ve run into another lovely Symfony2 bug/error. I have a functional test with a body as such:

      public function testFoo()
      {
          $client = self::createClient();
          $client->request('GET', '/foo/');
          $this->assertEquals( 403, $client->getResponse()->getStatusCode() );
      }

      FYI I’m trying to test access roles are working correctly so I should get a 403 forbidden.

      And my relevant config_test.yml:

      imports:
          - { resource: config_dev.yml }
          - { resource: parameters_test.ini }
      
      framework:
          router:   { resource: "%kernel.root_dir%/config/routing_test.yml" }
          test:     ~

      Upon running this test I get the lovely error:

      Warning: session_start(): The session id is too long or contains illegal characters, valid characters are a-z, A-Z, 0-9 and '-,' in /home/james/foo/vendor/symfony/src/Symfony/Component/HttpFoundation/SessionStorage/NativeSessionStorage.php line 87 (500 Internal Server Error)

      After messing with the config and looking around, it turns out this is a known issue. For whatever reason, my config was missing some required session parameters for testing. Have a look at https://github.com/symfony/symfony/issues/1759 for more info. To fix it, I had to change the session storage method params in config_test.yml from native (inherited) to filesystem:

      framework:
          router:   { resource: "%kernel.root_dir%/config/routing_test.yml" }
          test:     ~
          session:
            storage_id: session.storage.filesystem

      Now, the exception is gone and the test runs:

      $ phpunit --stop-on-error --stop-on-failure -c app src/Foo/Bundle/SecurityBundle/
      PHPUnit 3.5.5 by Sebastian Bergmann.
      
      ..F
      
      Time: 1 second, Memory: 29.75Mb
      
      There was 1 failure:
      
      1) Foo\Bundle\FooBundle\Tests\Functional\SecurityTest::testFoo
      Failed asserting that  matches expected .
      
      /home/james/code/Foo/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php:59
      
      FAILURES!
      Tests: 3, Assertions: 4, Failures: 1.

      Great, next problem…

      • views
      • Tweet
    • 2
      23 Nov 2011

      Symfony2 FOSUserBundle Roles - a simple solution

      • Edit
      • Delete
      • Tags
      • Autopost

      Out of the box, FOSUserBundle does NOT support Doctrine ORM database persisted Roles. The base class they give you to extend has functionality for roles, i.e. setting, getting and checking them, but you will need to write your own code to persist to the database and thus fully equipping the User with role functionality.

      The quickest and easiest way to get roles up and running is to use the existing FOS functionality and create your user a new roles field, mapping it as an array type in Doctrine. This is quicker and easier to work with than using separate Role objects. So your user class will need to specify the $roles class member with the following mapping metadata:

      /**
       * @ORM\Column(type="array", name="roles")
       */
       protected $roles;

      This will tell doctrine to map a PHP array to a SQL CLOB using serialize() and unserialize(). Take a look at the Doctrine mapping documentation at http://www.doctrine-project.org/docs/orm/2.0/en/reference/basic-mapping.html#...

      Schema changes

      So now the model has been sorted, the new column needs to be added to the database. Generate a migration for the changes to the users table:

      $ php app/console doctrine:migrations:diff
      Generated new migration class to "/home/james/fooapp/DoctrineMigrations/Version20111121145741.php" from schema differences.

      This will generate said file which will contain a call to a method to add the new column:

      $this->addSql("ALTER TABLE users ADD roles LONGTEXT NOT NULL COMMENT '(DC2Type:array)'");

      If you want to set up default roles for existing users, under the call to addSql() insert the following line:

      $this->addSql(sprintf("UPDATE users SET roles = '%s'", 'a:1:{i:0;s:8:"ROLE_FOO";}'));

      Then execute:

      $ php app/console doctrine:migrations:migrate
      ...
      Migrating up to 20111121145921 from 0
      
        ++ migrating 20111121145921
      
           -> ALTER TABLE users ADD roles LONGTEXT NOT NULL COMMENT '(DC2Type:array)'
           -> UPDATE users SET roles = 'a:1:{i:0;s:8:"ROLE_FOO";}'
      
        ++ migrated (0.69s)
      
        ------------------------
      
        ++ finished in 0.69
        ++ 1 migrations executed
        ++ 2 sql queries

      (More information on migrations can be found at http://symfony.com/doc/2.0/bundles/DoctrineMigrationsBundle/index.html and https://github.com/symfony/DoctrineMigrationsBundle)

      This will add the new column to your users table. You now need to create a new user which will save a serialized empty array in the roles column. From here you can add new roles to that user or you may need to sort out existing users which will have nothing in their roles field unless you followed the step above. Trying to update or work with existing users with empty roles fields will most likely give you a Doctrine ConversionException:

      [Doctrine\DBAL\Types\ConversionException]                   
      Could not convert database value "" to Doctrine Type array

      So I suggest setting up a new user with default roles for your system then copying the content of that user’s roles field into existing user’s roles fields.

      To test the roles functionality try the following:

      $ php app/console fos:user:create jamestest james@foo.co.uk mypassword
      Created user jamestest
      $ php app/console fos:user:promote jamestest --super
      User "jamestest" has been promoted as a super administrator.

      If you now check your db, the roles field for jamestest should look similar to:

      a:2:{i:0;s:8:"ROLE_FOO";i:1;s:16:"ROLE_SUPER_ADMIN";}

      Your User should now be set up to persist it’s roles to the database. You should be able to edit the security.yml file and restrict access to certain parts of your site by url pattern:

      access_control:
        - { path: ^/admin, roles: ROLE_FOO }

      For further information see http://symfony.com/doc/2.0/book/security.html#securing-specific-url-patterns

      UPDATE:

      See my following article if you need more powerful Role entity based solution http://blog.jmoz.co.uk/symfony2-fosuserbundle-role-entities

      • views
      • Tweet
    • 2
      7 Sep 2011

      PHP news

      • Edit
      • Delete
      • Tags
      • Autopost

      I've noticed recently a lack of PHP news/articles/blog posts coming to my attention.  I'm sure I used to see a constant stream of them but I can't remember my sources.  Maybe its my increasing reliance on Twitter as a source of everything - general news, local info, tech news and banter with friends.  I used to use Google Reader a lot, and sites such as Digg, Popurls, Mixx etc but they all seem to have dropped in quality or they get increasingly harder to manage (GReader rss feeds) whilst everywhere else seems to have a Twitter feed.  So this is more of a request for comments or tips on decent sources of PHP news.  You can reply by comment or tweet me at @jwm0z.  I'll list some of mine:

      Twitter

      Any tech site or person that tweets about PHP I tend to follow, I have a twitter tech list (which probably could do with being split into a php list) and I also watch a #php and #symfony2 search in my Tweetdeck.

      Web Sites

      One of my favourite sites to follow is PHP Developer, they reblog a lot PHP articles from developer blogs (including mine) and they have a busy twitter feed.

      Some others include Planet PHP, DZone PHP, Zend Developer Zone but I never seem to catch their updates as often as I do as PHP Developer's.

      Too many channels

      There's just too many channels of information available to be able to get a good overview of the PHP scene.  For me, Twitter is probably the easiest to manage but I need to follow more decent sources of technical articles and news.  If you've got any lists to share or think it's worthwhile me following you, please get in touch!

      • views
      • Tweet
    • 0
      5 Sep 2011

      A Symfony2 console Command and the Foursquare API venuehistory

      • Edit
      • Delete
      • Tags
      • Autopost

      I've been playing with the Foursquare API recently, I'm attempting to get a new homepage built and want to display a map of where I hang out. I use Foursquare quite a bit so wanted to get the locations from their API then plot them on Google maps.

      There's a venuehistory endpoint that gives you a massive list of all the venues you've checked into with details such as the venue name, location including lat and lng coordinates, and the count of times you've checked in.

      Here's a screenshot of their json response.

      Media_httpdldropboxco_gdpci

      I want to pull this into my own database so I have a local copy of it and can play around with it. I'm using Symfony2 so I've created a console command which I can run on a cron, or on demand. I decided to do it this way as mainly a learning exercise and also this is a bit better than a standard php script.

      The command hits the endpoint with my Oauth access token (outside of the scope of this article, but see the docs for details) and then simply loops over the response and creates a load of Checkin entities which the entity manager then persists.

      The command takes an optional argument for the access_token or otherwise defaults to a parameter. There's also an optional flag to disable the truncation of the checkins table. You can see how to set these up along with the help text in the configure() method. Output is handled by the call to $output->writeln() and you can see how highlighting works in the screenshot of the output.

      Media_httpdldropboxco_rkcdq

      I may write a future post showing how to push the Foursquare code into a service based implementation so look out.

      • views
      • Tweet
    • Search

    • My Sites

      • James Morris
      • Mixcloud Downloader
      • Shoreditch Vietnamese
      • I HEART Shoreditch
    • Tags

      • php
      • ubuntu
      • FOSUserBundle
      • clouddownload.co.uk
      • roles
      • shoreditch
      • symfony
      • 301
      • SplObserver
      • SplSubject
      • android
      • api
      • codename
      • command
      • console
      • contract
      • cucumber
      • delete
      • docky
      • dotdeb
      • errors
      • exif
      • firewatir
      • foursquare
      • http
      • imagick
      • iphone
      • mario
      • maverick
      • mixcloud
      • netbeans
      • news
      • nginx
      • observer
      • php 5.3
      • pidgin
      • poker planning
      • put
      • ruby
      • scrum
      • session
      • shit
      • shoreditchvietnamese.co.uk
      • skype
      • stripImage
      • sudo gem update --system
      • super hub
      • twitter
      • version
      • vietnamese
      • virgin media
      • watir
    • Archive

      • 2011 (22)
        • December (3)
        • November (4)
        • September (3)
        • August (3)
        • July (1)
        • May (2)
        • April (4)
        • March (1)
        • January (1)
      • 2010 (1)
        • April (1)
    • Contributors

      James Morris
    • Obox Design
  • James Morris - JMOZ


    30383 Views
  • Get Updates

    Subscribe via RSS
    TwitterFacebookLinkedIn