Oliverde8's blue Website

Symfony_Sonata05-300x230.jpg

Sonata, MongDB & Symfony 3.3 on PHP7.1

I wanted to know if it was possible to use Sonata with mMngodb. After some research I found out that it was indeed possible. 

If there is one thing I love it's discovering new things, trying out new things. There is so much I would like todo, sadly weekends are to shorty for all the experiments I wish to run.
I would like to write about those experiments more often, but it takes quite a lot of time to write these articles; so usually I don't write much about it. 

But getting mongodb sf3.3 with sonata working without a mysql server has been slightly more complicated then expected, because the documentation was not that complete and there isn't any stable release of some of the bundles needed.

Before setting sail, I will not go into the details, I assume you have already installed a sonata with doctrine odm and that you are familliar with the process. 

Also I used many elements from the sonata project user bundle installation guide.

What you need to installed

I did my install on a PHP 7.1 (thanks docker to make it so easy). But it should work the same on 7.0 normally. 

You will of course need a brand new SF3.3, install with the fallowing packages : 

Doctrine

We need doctrine to get our database sorted out.

Doctrine doesen't work with the latest php 7+ mongodb driver, which means we need another package to add compatibility

Sonata

We also need sonata for our BO.

Sadly we have to use dev-master and not any tags. There are fixes for SF3.3 and also for PHP7.1 that's not available in any tags. Latest version will simply not work. This is where I lost most of my time as I tried not to use any dev versions.

Mongdb ACL bundle

Well normally acl are handled in the orm database. But as we wish to use mongodb. Again we will need a bundle in dev-master

This bundles composer.json says it isn't compatible with php 7.1. The issue is this bundle seems to be abandoned. But I couldn't find anything similar not abandoned. So let's use composer with --ignore-platform-reqs to get this bundle installed.

End result

"require": {
    "doctrine/doctrine-bundle": "^1.6",
    "doctrine/mongodb-odm": "^1.1.6",
    "alcaeus/mongo-php-adapter": "^1.1.2",
    "incenteev/composer-parameter-handler": "^2.0",
    "sensio/distribution-bundle": "^5.0.19",
    "sensio/framework-extra-bundle": "^3.0.2",
    "sonata-project/doctrine-mongodb-admin-bundle": "dev-master",
    "sonata-project/user-bundle": "dev-master",
    "symfony/monolog-bundle": "^3.1.0",
    "symfony/polyfill-apcu": "^1.0",
    "symfony/swiftmailer-bundle": "^2.3.10",
    "symfony/symfony": "3.3.*",
    "twig/twig": "^1.0||^2.0",
    "predis/predis": "^1.1.1",
    "pwalkow/mongodb-acl-bundle": "dev-master"
},

Other bundles such as FOSUserBundle has been installed at this stage. Let's no start configuring our SF.

Getting truely started

Enable the proper bundles. 

Modify your AppKernel to add the fallowing bundles.

// Dependency bundles
new \Knp\Bundle\MenuBundle\KnpMenuBundle(),
new \FOS\UserBundle\FOSUserBundle(),
new \PWalkow\MongoDBAclBundle\MongoDBAclBundle(),

// Sonata bundles
new \Sonata\CoreBundle\SonataCoreBundle(),
new \Sonata\BlockBundle\SonataBlockBundle(),
new \Sonata\EasyExtendsBundle\SonataEasyExtendsBundle(),
new \Sonata\AdminBundle\SonataAdminBundle(),
new \Sonata\DoctrineMongoDBAdminBundle\SonataDoctrineMongoDBAdminBundle(),
new \Sonata\UserBundle\SonataUserBundle('\'FOSUserBundle\''),

Configuration (app/config/config.yml)

Remove the symfony default doctrine orm configuration and add mongodb configuration

doctrine_mongodb:
    connections:
        default:
            server: "%mongodb_server%"
            options: {}
    default_database: "%mongodb_database%"
    document_managers:
        default:
            auto_mapping: true

Configure the acl storage :

pwalkow_mongo_db_acl:
    acl_provider:
        default_database: %mongodb_database%

Let's note that I have used a intermediate variable mongodb_database & server that will need to be set in the parameters.yml

We need to configure fos user bundle as well

fos_user:
    from_email:
        address : [email protected]
        sender_name: toto
    db_driver:      mongodb
    firewall_name:  main
    user_class:     Sonata\UserBundle\Entity\BaseUser

    group:
        group_class:   Sonata\UserBundle\Entity\BaseGroup
        group_manager: sonata.user.mongodb.group_manager

    service:
        user_manager: sonata.user.mongodb.user_manager

We are going to lake some changes here later. For now let's continue with our configuration. 

Finally we need to configure sonata

sonata_block:
    default_contexts: [cms]
    blocks:
        # enable the SonataAdminBundle block
        sonata.admin.block.admin_list:
            contexts: [admin]

sonata_user:
    security_acl: true
    manager_type: mongodb

This are quick configurations to get things going, you will need to make changes. 

Routing (app/config/routing.yml)

We simply add the sonata routes. 

sonata_user_security:
    resource: "@SonataUserBundle/Resources/config/routing/sonata_security_1.xml"

sonata_user_resetting:
    resource: "@SonataUserBundle/Resources/config/routing/sonata_resetting_1.xml"
    prefix: /resetting

sonata_user_profile:
    resource: "@SonataUserBundle/Resources/config/routing/sonata_profile_1.xml"
    prefix: /profile

sonata_user_register:
    resource: "@SonataUserBundle/Resources/config/routing/sonata_registration_1.xml"
    prefix: /register

sonata_user_change_password:
    resource: "@SonataUserBundle/Resources/config/routing/sonata_change_password_1.xml"
    prefix: /profile

sonata_user_admin_security:
    resource: '@SonataUserBundle/Resources/config/routing/admin_security.xml'
    prefix: /admin

sonata_user_admin_resetting:
    resource: '@SonataUserBundle/Resources/config/routing/admin_resetting.xml'
    prefix: /admin/resetting

You could also use the fos routes, both will work.

We are now missing the security configuration.

Security (app/config/security.yml)

security:
    role_hierarchy:
        ROLE_ADMIN:       [ROLE_USER, ROLE_SONATA_ADMIN]
        ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
        SONATA:
            - ROLE_SONATA_PAGE_ADMIN_PAGE_EDIT  # if you are using acl then this line must be commented

    providers:
        fos_userbundle:
            id: fos_user.user_manager

    firewalls:
        # Disabling the security for the web debug toolbar, the profiler and Assetic.
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        # -> custom firewall for the admin area of the URL
        admin:
            pattern:            /admin(.*)
            context:            user
            form_login:
                provider:       fos_userbundle
                login_path:     /admin/login
                use_forward:    false
                check_path:     /admin/login_check
                failure_path:   null
            logout:
                path:           /admin/logout
                target:         /admin/login
            anonymous:          true
        # -> end custom configuration

        # default login area for standard users
        # This firewall is used to handle the public login area
        # This part is handled by the FOS User Bundle
        main:
            pattern:             .*
            context:             user
            form_login:
                provider:       fos_userbundle
                login_path:     /login
                use_forward:    false
                check_path:     /login_check
                failure_path:   null
            logout:             true
            anonymous:          true

    access_control:
        # URL of FOSUserBundle which need to be available to anonymous users
        - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
        # Admin login page needs to be accessed without credential
        - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/login_check$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
        # Secured part of the site
        # This config requires being logged for the whole site and having the admin role for the admin part.
        # Change these rules to adapt them to your needs
        - { path: ^/admin/, role: [ROLE_ADMIN, ROLE_SONATA_ADMIN] }
        - { path: ^/.*, role: IS_AUTHENTICATED_ANONYMOUSLY }

    acl:
        provider: mongodb_acl_provider

Basically this is the exact same as what has been done in the sonata documentation : https://sonata-project.org/bundles/user/3-x/doc/reference/installation.html#integrating-the-bundle-into-the-sonata-admin-bundle

With the diffrence of the acl provider being 

mongodb_acl_provider.

At this point everyhing is nearly working, but if you try to open a page sf it going to tell you that mongodb_acl_provider is not know. We need to register a new service. To have it simple let's put this in the app/config/services.yml file

Adding Mongodb acl service

A simple service declaration is sufficient : 

    mongodb_acl_provider:
        parent: doctrine_mongodb.odm.security.acl.provider
        abstract: false
        autowire: false
        autoconfigure: false
        public: true

At this stage we are nearly ready. 

Extending Sonata User bundle

We need to generate the proper entities. 

php app/console sonata:easy-extends:generate SonataUserBundle -d src

Edit the AppKernetl to add our new bundle

new Application\Sonata\UserBundle\ApplicationSonataUserBundle(),

And update fos configuration to use the new entity, we only edit the fallowing configurations. 

fos_user:
    user_class:     Application\Sonata\UserBundle\Entity\User
    group:
        group_class:   Application\Sonata\UserBundle\Entity\Group

 

We are nearly there, let's now create our mongodb indexes. 

Preparing indexes

bin/console doctrine:mongodb:schema:update

We also need to init the acl's; 

docker-compose exec php bin/console init:acl:mongodb

And you are good to go. 

Errors I encountered

Sadly you are going to encounter a few errors in the sonata interface when trying to edit the users. 

I had to create a getGenderList method in the ApplicationSonataUserBudnles/Mode/User 

    public function getGenderList()
    {
        return ['male' => 'Male', 'female' => 'Female'];
    }

Last I had an error with the groups not being able to render propelry, and ended up removing it from the admin form of the users. 

Conclusions

Using Sonata on a orm project is quite easy, once all is installed everything works fine and the tutorials are straight forward. But on mongodb it's not so, there a re quite a few things not documented. The acl provider is the most annoying one of the bunch. Even in the latest symfony 3.3 documentation this is not propelry documented as it links to a unmaintained version. I believe majority of the install used mongodb & mysql together and therfore doesen't require acl stored in mongo. 

I would like to point that it has been a month already since I actually did this and some new versions might be out there not requiring you to use the dev-master. I haven't had time to check. 

Globally this install works well, but the sonata mongodb admin bundle is still not 100%. For exemple for some reasons embeded documens displayed as a table works great but the standard display is ugly.  I ended up using the views of the admin bundle for collections which works and looks much better.