Gotcha: Upgrading To FOSRestBundle 2.0

This is not a huge issue, and likely quite easy to identify and fix. But I’m the kind of person who hits Google before engaging brain, and I didn’t find a direct answer (boo, wake up brain!).

I have, this very evening, decided to try FOSRestBundle 2.0. It’s currently still in development, so isn’t available without a little naughty composer tagging:

    "require": {
        "friendsofsymfony/rest-bundle": "^2.0@dev"
    },

A little tip there – if you use the @dev after your requested version number, you can force through development packages without globally changing your project’s minimum-stability  setting.

Anyway, being that I’ve been coding for the last 12.5 hours, I went with the lazy man’s option of copy / pasting my config from a FOSRestBundle 1.7 installation I happened to have laying around.

fos_rest:
    view:
        exception_wrapper_handler:  null
        mime_types:
            json: ['application/json', 'application/x-json']
            jpg: 'image/jpeg'
            png: 'image/png'

Firstly, I got this:

InvalidConfigurationException in ArrayNode.php line 317:
Unrecognized option "exception_wrapper_handler" under "fos_rest.view"

Which confuses me, as this seems to be the same as what’s in the configuration reference.

Anyway, simply removing that line makes the error go away.

The next error I got was this:

Warning: array_merge(): Argument #1 is not an array
500 Internal Server Error - ContextErrorException

And what this turns out to be is that the mime_types must now all be arrays:

fos_rest:
    view:
        mime_types:
            json: ['application/json', 'application/x-json']
            jpg: ['image/jpeg']
            png: ['image/png']

So there you go 🙂

How I Fixed: File is absent cannot continue

Lately I’ve been tidying up my Ansible playbook scripts ahead of a forthcoming project deployment. Lots of change, including upgrading to PHP7 across the board.

Pretty cool, and exciting, but quite a lot of work all the same.

I decided to take the opportunity to restructure my log files. I mean, that’s a good way to spend a Friday night, right? Of course it is.

The relevant part of the nginx playbook is:

    - name: "create access log file"
      file: dest="/var/log/nginx/{{ item.directory }}-access.log"
            mode=644
            state=file
            owner="{{ nginx_user }}"
            group="{{ nginx_group }}"
      with_items: "{{ website_directories }}"

It’s admittedly been a while since I’ve been regularly hands-on with my Ansible setup, so I was a little rusty. See if you can spot the error in the above.

The with_items section lives in my host_vars/my-symfony-3.dev file, and looks like:

website_directories:
  - { name: "Root", directory: "{{ website_domain_name }}" }
  - { name: "API", directory: "api.{{ website_domain_name }}" }

So, anyway the error that was being spat out was as follows:

TASK [create access log file] **************************************************
failed: [my-symfony-3.dev] (item={u'stat': {u'exists': False}, '_ansible_item_result': True, '_ansible_no_log': False, u'changed': False, 'item': {u'directory': u'my-symfony-3.dev', u'name': u'Root'}, 'invocation': {'module_name': u'stat', u'module_args': {u'checksum_algorithm': u'sha1', u'mime': False, u'get_checksum': True, u'path': u'/var/www/my-symfony-3.dev', u'follow': False, u'get_md5': True}}}) => {"failed": true, "item": {"changed": false, "invocation": {"module_args": {"checksum_algorithm": "sha1", "follow": false, "get_checksum": true, "get_md5": true, "mime": false, "path": "/var/www/my-symfony-3.dev"}, "module_name": "stat"}, "item": {"directory": "my-symfony-3.dev", "name": "Root"}, "stat": {"exists": false}}, "msg": "file (/var/log/nginx/my-symfony-3.dev-access.log) is absent, cannot continue", "path": "/var/log/nginx/my-symfony-3.dev-access.log", "state": "absent"}
failed: [my-symfony-3.dev] (item={u'stat': {u'exists': False}, '_ansible_item_result': True, '_ansible_no_log': False, u'changed': False, 'item': {u'directory': u'api.my-symfony-3.dev', u'name': u'API'}, 'invocation': {'module_name': u'stat', u'module_args': {u'checksum_algorithm': u'sha1', u'mime': False, u'get_checksum': True, u'path': u'/var/www/api.my-symfony-3.dev', u'follow': False, u'get_md5': True}}}) => {"failed": true, "item": {"changed": false, "invocation": {"module_args": {"checksum_algorithm": "sha1", "follow": false, "get_checksum": true, "get_md5": true, "mime": false, "path": "/var/www/api.my-symfony-3.dev"}, "module_name": "stat"}, "item": {"directory": "api.my-symfony-3.dev", "name": "API"}, "stat": {"exists": false}}, "msg": "file (/var/log/nginx/api.my-symfony-3.dev-access.log) is absent, cannot continue", "path": "/var/log/nginx/api.my-symfony-3.dev-access.log", "state": "absent"}

Hopefully it make it easier to Google for this for someone in the future.

Anyway, the solution will make you kick yourself. I can’t imagine that many people will make this typo, but you never know:

    - name: "create access log file"
      file: dest="/var/log/nginx/{{ item.directory }}-access.log"
            mode=644
            state=touch
            owner="{{ nginx_user }}"
            group="{{ nginx_group }}"
      with_items: "{{ website_directories }}"

    - name: "create error log file"
      file: dest="/var/log/nginx/{{ item.directory }}-error.log"
            mode=644
            state=touch
            owner="{{ nginx_user }}"
            group="{{ nginx_group }}"
      with_items: "{{ website_directories }}"

Yeah… d’oh. RTFM.

Though to be fair, I had RTFM, it was just a long time ago, and I guess I rely too heavily on IDE code completion. When it comes to Sublime + YAML, I am going to make mistakes 🙂

If this looks interesting to you, and you want to know more about Ansible, be sure to check out the tutorial series I did here at Code Review Videos.

It’s shameless self promotion, I know, but hey, it is free. And hopefully you find it useful.

Renaming Routes in DunglasApiBundle

Let’s say you have defined a new Resource  entry for your SocialMediaAccount  Entity:

# app/config/services.yml

    resource.social_media_account:
        parent:    "api.resource"
        arguments: [ "AppBundle\\Entity\\SocialMediaAccount" ]
        tags:      [ { name: "api.resource" } ]

Now, when we run php app/console debug:router we see the following:

dunglas-api-bundle-rename-routes

Ack. Underscores in our URLs.

Most likely not what we want.

Thankfully, changing this is really easy:

# app/config/services.yml

    resource.social_media_account:
        parent:    "api.resource"
        arguments: [ "AppBundle\\Entity\\SocialMediaAccount" ]
        calls:
            - method: "initShortName"
              arguments: [ 'social-media-account' ]
        tags:      [ { name: "api.resource" } ]

Success:

dunglasapibundle-custom-route-names

But, how did I know to add in that specific method in the calls block?

This line: parent: “api.resource” tells us that we are using a Parent Service.

Parent services are something of a trade off.

They decrease the amount of configuration you are required to write, but increase the technical complexity of comprehending the configuration.

The idea is that if we have two or more classes that are being configured as Symfony Services, and that they share identical setup requirements, then we can extract the common / shared requirements to a parent class, and tell our child services to use the configuration from the parent.

We can now remove the duplicated service setup / configuration.

However, newer or less experienced developers who read our code / configuration will have a harder time understanding what is happening.

But back to DunglasApiBundle!

We are using api.resource as our Parent service. This service definition must live somewhere, and as Symfony Bundles tend to follow a common structure, the best place to start looking would be in:

/vendor/dunglas/api-bundle/Resources/config/

There’s a fair few files in here:

dunglas-api-bundle-resources

If unsure, do a quick search for ‘api.resources’ in that folder, or resort to the old school method of opening them one-by-one and eye balling the service definitions until you find the one you want.

In our case, it lives inside api.xml , nice and easy:

<service id="api.resource" class="Dunglas\ApiBundle\Api\Resource" public="false" abstract="true" lazy="true" />

Now we have the class name so we know exactly where to look.

With the file found (/vendor/dunglas/api-bundle/Api/Resource.php ), all we need to know are the available method names. Thankfully, PHPStorm can provide us with a nice list of them:

dunglas-api-bundle-resource-php-methods

Sorry about the truncating. If you want to see that view then hit cmd / ctrl + 7, and then cmd / ctrl + 1 to return to ‘normal’ when you are done.

Now we have our list of methods, we can simply add the required methods, along with their parameters to our calls block in our concrete Resource setup:

# app/config/services.yml

    resource.social_media_account:
        parent:    "api.resource"
        arguments: [ "AppBundle\\Entity\\SocialMediaAccount" ]
        calls:
            - method: "initShortName"
              arguments: [ 'social-media-account' ]
        tags:      [ { name: "api.resource" } ]

Be sure to read up on Symfony Parent Services to understand this further.

Happy Dunglas’ing! 🙂