How I Fixed: TypeError: ‘null’ is not an object (evaluating ‘currentSpec.$injector’)

 

I hit upon a problem in testing an Angular project this week that had me stumped for a while. The problem was this:

Every time I ran the test suite as a whole, they failed.

But if I ran each test file on its own… individually, they would pass.

I find more and more of my time lately is spent dealing with these sorts of things – they aren’t ‘development’ tasks, just annoyances that keep me from doing the thing I enjoy most – writing code.

Anyway, here’s the error:

INFO [PhantomJS 1.9.8 (Linux 0.0.0)]: Connected on socket DGb_Xn6WLWQNnEEkUwtk with id 45807911
PhantomJS 1.9.8 (Linux 0.0.0) LOG: 'no config'
 
PhantomJS 1.9.8 (Linux 0.0.0) LOG: 'no config'
 
PhantomJS 1.9.8 (Linux 0.0.0) LOG: 'no config'
 
PhantomJS 1.9.8 (Linux 0.0.0) UrlManager should have get function FAILED
        TypeError: 'null' is not an object (evaluating 'currentSpec.$injector')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23390:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2330:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'null' is not an object (evaluating 'currentSpec.$injector')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23390:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2330:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'null' is not an object (evaluating 'currentSpec.$injector')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23390:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2330:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'null' is not an object (evaluating 'currentSpec.$modules')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23514:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2454:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'undefined' is not an object (evaluating 'UrlManager.get')
            at /tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:42035:0 <- /my/project/src/components/UrlManager/spec/UrlManagerSpec.js:26:0
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
PhantomJS 1.9.8 (Linux 0.0.0) UrlManager should return object for the others FAILED
        TypeError: 'null' is not an object (evaluating 'currentSpec.$injector')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23390:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2330:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'null' is not an object (evaluating 'currentSpec.$injector')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23390:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2330:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'null' is not an object (evaluating 'currentSpec.$injector')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23390:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2330:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'null' is not an object (evaluating 'currentSpec.$modules')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23514:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2454:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'undefined' is not an object (evaluating 'UrlManager.get')
            at /tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:42053:0 <- /my/project/src/components/UrlManager/spec/UrlManagerSpec.js:44:0
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
           at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
PhantomJS 1.9.8 (Linux 0.0.0): Executed 7 of 7 (2 FAILED) (0.415 secs / 0.026 secs)
 
=============================== Coverage summary ===============================
Statements   : 17.48% ( 482/2758 )
Branches     : 4.14% ( 40/967 )
Functions    : 4.65% ( 30/645 )
Lines        : 17.62% ( 477/2707 )
================================================================================
[13:41:23] 'test' errored after 8.21 s
[13:41:23] Error in plugin 'test'
Message:
    Karma test returned 1
blimpyboy@project-dev1:~/Development/project$ ^C
blimpyboy@project-dev1:~/Development/project$ ^C
blimpyboy@project-dev1:~/Development/project$ gulp test
[13:42:58] Warning: gulp version mismatch:
[13:42:58] Global gulp is 3.9.0
[13:42:58] Local gulp is 3.8.11
[13:42:59] Using gulpfile ~/Development/project/gulpfile.js
[13:42:59] Starting 'test'...
INFO [framework.browserify]: Paths to browserify
    /my/project/src/components/PubSub/spec/**/*.js
    /my/project/src/components/UrlManager/spec/**/*.js
INFO [framework.browserify]: Browserified in 6543ms, 6524kB
INFO [karma]: Karma v0.12.37 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.8 (Linux 0.0.0)]: Connected on socket VrUi5MBGOl0J1oHWVKNw with id 50398227
PhantomJS 1.9.8 (Linux 0.0.0) LOG: 'no config'
 
PhantomJS 1.9.8 (Linux 0.0.0) LOG: 'no config'
 
PhantomJS 1.9.8 (Linux 0.0.0) LOG: 'no config'
 
PhantomJS 1.9.8 (Linux 0.0.0): Executed 7 of 7 SUCCESS (0.056 secs / 0.035 secs)
 
=============================== Coverage summary ===============================
Statements   : 20.01% ( 552/2758 )
Branches     : 5.07% ( 49/967 )
Functions    : 6.98% ( 45/645 )
Lines        : 20.21% ( 547/2707 )
================================================================================
[13:43:07] Finished 'test' after 8.1 s
blimpyboy@project-dev1:~/Development/project$

I’m aware the coverage isn’t so good – but actually this is not the true coverage as I’d stripped out a whole bunch of tests by bastardising the karma.conf.js file to try and isolate the problem. No… seriously, I promise 🙂

Anyway, it turned out that the solution to this was actually pretty simple.

Of course, nearly all solutions to programming problem seem simple once you have figured out the problem. Hindsight is such a wonderful thing.

But in this case, the problem was that a bunch of variables had been declared inside on the of describe blocks:

describe("SomeModule module", function () {

    beforeEach(angular.mock.module('Some.Module'));

    var angular = require('angular');
    var scope;
    require('../../../scripts/app.js');
    require('angular-mocks');

    var SomeModule;

And the solution was to simply move all the setup stuff outside of the describe block:

var angular = require('angular');
var scope;
require('../../../scripts/app.js');
require('angular-mocks');

var SomeModule;

describe("SomeModule module", function () {

    beforeEach(angular.mock.module('Some.Module'));

An easy fix.

The real annoyance here was that I went through this whole project alphabetically, and this particular module began with the letter ‘P’, so I’d been through over half the code before I spotted it. Hours I will never get back.

Still, it’s fixed now, and hopefully now you can save a few hours if you ever suffer from this problem yourself.

 

Elixir: Power of Erlang, Joy of Ruby by Dave Thomas

I don’t get to as many conferences as I would like. This year it looks like I’m only going to PHPNW (again), largely due to an expanding family 🙂 and work commitments 🙁

But to get around this, I try to watch a talk a week on YouTube.

Up until now I’ve only really tweeted about talks sporadically, but I figured some are so good they warrant a little more than 140 characters can provide.

This week I watched: LoneStarRuby Conf 2013 – Elixir: Power of Erlang, Joy of Ruby by Dave Thomas

This is partly because of Hacker News. I am a total sucker for following all the trends on there. Thankfully the relentless / weekly JavaScript framework fad has calmed down on there now, so I’m becoming a little more adventurous.

Lately, there have been more and more threads (yes, this was a poor attempt at an Erlang pun) and I’ve been wanting to at least see Erlang / Elixir / Phoenix in action.

As you might have guessed, I’m a fan of video learning, especially when I’m first getting started. It’s so much easier to see some high level ‘stuff’ rather than have to read pages and pages of text. The text is inevitable, in my opinion, but I like to read it only after seeing it.

Now, my general knowledge of the Ruby community is that there’s some guy called @dhh who is quite the racing driver, and then there’s a bunch of cool cats who code in Ruby. Yup, and that’s about me all out of Ruby knowledge.

Oh, I also remembered that Ruby sometimes makes Vagrant hard to use, and also Sandi Metz (whose talks are awesome btw).

But I must admit, my knowledge of other key figures in the scene are none existent.

The upshot is, I really had no idea who Dave Thomas is, prior to watching this talk. But after enjoying his talk, and doing a little digging, thankfully he seems to have tons more to see. Awesome.

There are two really nice parts to this video / talk.

The first: live coding. Full credit. That’s scary, and difficult to pull off.

I actually like it when things don’t quite go to script in live coding sessions. It reveals more than a scripted section can, as you often get to hear their spoken thought processes. There’s a good live debugging session in here, and I found that insightful.

The second: I kinda don’t want to spoil this for you. But when Dave uses pmap towards the end of his talk (35m mark onwards)… wow. That was so, so cool.

I am genuinely curious as to whether Erlang / Elixir with Phoenix are going to be a good fit for me. My next step is to take the tutorials. After watching this video I can’t wait.

 

How I Fixed: CSRF Token Is Invalid

There’s an obvious fix, and a not so obvious fix to this problem – The CSRF Token Is Invalid. Please try to resubmit the form:

The CSRF Token Is Invalid. Please try to resubmit the form.
pesky.

The ‘obvious’ fix is that you may very well have forgotten to add in:

{{ form_end(yourFormNameHere) }}

To your twig form template file.

It’s easy to do, and we’ve all done it.

You may see this as:

{{ form_rest(yourFormName) }}
{{ form_end(yourFormName) }}

Also, but as of Symfony 3 at leastform_rest is now added in to form_end for free. It may have arrived earlier, but it’s late now, and I’m too tired to check.

Anyway, if that all works then perfect, and off you go.

However, the less obvious problem might be that your session directory is not writable by the web server user.

This just caught me out when setting up a new server.

I’d used Ansible to build my dev server, and then I’d also deployed a variant of my dev script to production.

However, somewhere along the way I’d boobed and created myself a var/sessions directory, and also a shared/var/sessions directory, and whilst the permissions where correct on one, they weren’t correct on the right one :/

Why might this be the case? Well, I deploy using Deployer, but I’d only just set that up to deploy to prod. During dev I simply work on the local VM – no deploy script needed. And at this stage I don’t have a staging box for  this project.

So yeah, make sure that whatever user your web server is running as – www-data in this case – also has permissions to write to whichever directory you are storing your session data in.

You can find this directory by looking in config.yml :

framework:
    # snip
    session:
        # http://symfony.com/doc/current/reference/configuration/framework.html#handler-id
        handler_id:  session.handler.native_file
        save_path:   "%kernel.root_dir%/../var/sessions/%kernel.environment%"

 

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 🙂

Upgrading to Ansible 2.1 on OSX

I have Ansible installed on a variety of machines – primarily Ubuntu, but also for setting up the virtual machines / servers I use when recording, I also have Ansible installed on OSX.

Up until today I was running Ansible v1.9.1, but then I hit on an issue with my nginx sites not creating properly (for some reason which I did not track down), and decided the best course of action would be to upgrade all the submodules I use as the basis of my playbook infrastructure.

Just to explain this a little further – when I first started using Ansible I would git clone any interesting repository to my local machine, and then take ownership of that repo into my own project. There are pros and cons to this approach:

Pros

  • I could make changes to the repos to suit my needs
  • I knew the repo contents wouldn’t change unless I changed them
  • Everything was very explicit and obvious to new people

Cons

  • I created a lot of work for myself

Yeah, ‘busy’ work such as managing an ever expanding list of other people’s projects was not a fun thing. Instead, I switched to ‘importing’ other peoples projects by way of git’s submodules, which works a treat, but there’s a drawback:

Other people have a habit of changing things.

My mistake was to blindly update all my submodules to their latest versions:

git submodule foreach 'git fetch origin; git checkout $(git rev-parse --abbrev-ref HEAD); git reset --hard origin/$(git rev-parse --abbrev-ref HEAD); git submodule update --recursive; git clean -dfx'

This worked fine, but it turned out that some of my tracked submodules now required Ansible 2 to run.

No problem, I thought, let’s just upgrade to Ansible 2.

How naive.

The first mistake I made in this process was to try and update an OSX installation of Ansible by way of using pip install ansible –upgrade.

Yeah… not so much. Plenty of errors here:

➜  ~ sudo pip install ansible --upgrade
Password:
The directory '/Users/codereview/Library/Caches/pip/http' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
The directory '/Users/codereview/Library/Caches/pip' or its parent directory is not owned by the current user and caching wheels has been disabled. check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
Collecting ansible
  Downloading ansible-2.1.0.0.tar.gz (1.9MB)
    100% |████████████████████████████████| 1.9MB 576kB/s
Requirement already up-to-date: paramiko in /Library/Python/2.7/site-packages (from ansible)
Requirement already up-to-date: jinja2 in /Library/Python/2.7/site-packages (from ansible)
Requirement already up-to-date: PyYAML in /Library/Python/2.7/site-packages (from ansible)
Collecting setuptools (from ansible)
  Downloading setuptools-23.0.0-py2.py3-none-any.whl (435kB)
    100% |████████████████████████████████| 440kB 2.3MB/s
Requirement already up-to-date: pycrypto>=2.6 in /Library/Python/2.7/site-packages (from ansible)
Requirement already up-to-date: pyasn1>=0.1.7 in /Library/Python/2.7/site-packages (from paramiko->ansible)
Requirement already up-to-date: cryptography>=1.1 in /Library/Python/2.7/site-packages (from paramiko->ansible)
Requirement already up-to-date: MarkupSafe in /Library/Python/2.7/site-packages (from jinja2->ansible)
Requirement already up-to-date: cffi>=1.4.1 in /Library/Python/2.7/site-packages (from cryptography>=1.1->paramiko->ansible)
Collecting six>=1.4.1 (from cryptography>=1.1->paramiko->ansible)
  Downloading six-1.10.0-py2.py3-none-any.whl
Requirement already up-to-date: idna>=2.0 in /Library/Python/2.7/site-packages (from cryptography>=1.1->paramiko->ansible)
Requirement already up-to-date: ipaddress in /Library/Python/2.7/site-packages (from cryptography>=1.1->paramiko->ansible)
Requirement already up-to-date: enum34 in /Library/Python/2.7/site-packages (from cryptography>=1.1->paramiko->ansible)
Requirement already up-to-date: pycparser in /Library/Python/2.7/site-packages (from cffi>=1.4.1->cryptography>=1.1->paramiko->ansible)
Installing collected packages: setuptools, ansible, six
  Found existing installation: setuptools 1.1.6
    Uninstalling setuptools-1.1.6:
Exception:
Traceback (most recent call last):
  File "/Library/Python/2.7/site-packages/pip/basecommand.py", line 215, in main
    status = self.run(options, args)
  File "/Library/Python/2.7/site-packages/pip/commands/install.py", line 317, in run
    prefix=options.prefix_path,
  File "/Library/Python/2.7/site-packages/pip/req/req_set.py", line 736, in install
    requirement.uninstall(auto_confirm=True)
  File "/Library/Python/2.7/site-packages/pip/req/req_install.py", line 742, in uninstall
    paths_to_remove.remove(auto_confirm)
  File "/Library/Python/2.7/site-packages/pip/req/req_uninstall.py", line 115, in remove
    renames(path, new_path)
  File "/Library/Python/2.7/site-packages/pip/utils/__init__.py", line 267, in renames
    shutil.move(old, new)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 299, in move
    copytree(src, real_dst, symlinks=True)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 208, in copytree
    raise Error, errors
Error: [('/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib/__init__.py', '/tmp/pip-ymaXaK-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib/__init__.py', "[Errno 1] Operation not permitted: '/tmp/pip-ymaXaK-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib/__init__.py'"), ('/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib/__init__.pyc', '/tmp/pip-ymaXaK-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib/__init__.pyc', "[Errno 1] Operation not permitted: '/tmp/pip-ymaXaK-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib/__init__.pyc'"), ('/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib/markers.py', '/tmp/pip-ymaXaK-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib/markers.py', "[Errno 1] Operation not permitted: '/tmp/pip-ymaXaK-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib/markers.py'"), ('/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib/markers.pyc', '/tmp/pip-ymaXaK-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib/markers.pyc', "[Errno 1] Operation not permitted: '/tmp/pip-ymaXaK-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib/markers.pyc'"), ('/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib', '/tmp/pip-ymaXaK-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib', "[Errno 1] Operation not permitted: '/tmp/pip-ymaXaK-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/_markerlib'")]

Which is honestly fair enough – this isn’t how I’d installed Ansible originally (my poor grep skills fooled me), and this led me down some dark alleys of trying to manually fudge the upgrade of dependencies I thought were at fault – pycrypto, paramiko, and others.

The command that worked was simple:

brew install ansible
➜  ~ ansible --version
ansible 2.1.0.0

Ok, so cool, up to date with Ansible, but the playbook still wouldn’t run properly.

➜  ansible-submodule git:(master) ✗ ansible-playbook -i hosts -l symfony-3-api.dev playbook/symfony-dev.yml -k -K -s
SSH password:
SUDO password[defaults to SSH password]:
[DEPRECATION WARNING]: Instead of sudo/sudo_user, use become/become_user and make sure become_method is 'sudo' (default).
This feature will be removed in a future release.
Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.

PLAY [all] *********************************************************************

TASK [setup] *******************************************************************
No handlers could be found for logger "paramiko.transport"
fatal: [symfony-3-api.dev]: UNREACHABLE! => {"changed": false, "msg": "'EntryPoint' object has no attribute 'resolve'", "unreachable": true}
	to retry, use: --limit @playbook/symfony-dev.retry

PLAY RECAP *********************************************************************
symfony-3-api.dev          : ok=0    changed=0    unreachable=1    failed=0

Quite frustrating. I knew I could SSH onto the box, so it was likely not my server at fault.

A simpler Ansible ping command helped a little:

➜  ansible-submodule git:(master) ✗ ansible symfony-3-api.dev -i hosts -m ping -vvvv
Using /Users/Shared/Development/ansible-submodule/ansible.cfg as config file
Loaded callback minimal of type stdout, v2.0
<192.168.1.64> ESTABLISH SSH CONNECTION FOR USER: deploy
<192.168.1.64> SSH: EXEC ssh -C -vvv -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=deploy -o ConnectTimeout=10 -o ControlPath=/Users/codereview/.ansible/cp/ansible-ssh-%h-%p-%r 192.168.1.64 '/bin/sh -c '"'"'LANG=en_GB.UTF-8 LC_ALL=en_GB.UTF-8 LC_MESSAGES=en_GB.UTF-8 /usr/bin/python && sleep 0'"'"''
symfony-3-api.dev | UNREACHABLE! => {
    "changed": false,
    "msg": "SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh",
    "unreachable": true
}

From here I found a helpful github issue which led me to this:

symfony-3-api.dev ansible_ssh_host=192.168.1.64 ansible_ssh_user=deploy ansible_connection=ssh

Note the inclusion of:

ansible_connection=ssh

But this needed a little further help to start working:

curl -O -L http://downloads.sourceforge.net/project/sshpass/sshpass/1.05/sshpass-1.05.tar.gz && tar xvzf sshpass-1.05.tar.gz
cd sshpass-1.05
./configure
make
sudo make install

After that, the ping and playbook command would run again.

➜  ansible-submodule git:(master) ✗ ansible symfony-3-api.dev -i hosts -m ping -vvvv
Using /Users/Shared/Development/ansible-submodule/ansible.cfg as config file
Loaded callback minimal of type stdout, v2.0
<192.168.1.64> ESTABLISH SSH CONNECTION FOR USER: deploy
<192.168.1.64> SSH: EXEC ssh -C -vvv -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=deploy -o ConnectTimeout=10 -o ControlPath=/Users/codereview/.ansible/cp/ansible-ssh-%h-%p-%r 192.168.1.64 '/bin/sh -c '"'"'LANG=en_GB.UTF-8 LC_ALL=en_GB.UTF-8 LC_MESSAGES=en_GB.UTF-8 /usr/bin/python && sleep 0'"'"''
symfony-3-api.dev | SUCCESS => {
    "changed": false,
    "invocation": {
        "module_args": {
            "data": null
        },
        "module_name": "ping"
    },
    "ping": "pong"
}

Jackpot.