Your unit test methods need docblocks too

If you've met me at any time in the previous 20 years and you discussed unit testing with me, chances are pretty big that I'd have told you that your test methods in your unit tests don't really need docblocks, because the test methods would be named in such a way that they were descriptive. In a unit test class you could have methods such as testCheckWithQueueThatHasMessagesButIsNotProcessingThemReturns500() or testCheckWithQueueThatHasCorrectValuesReturns200(). Those names give you a good idea of what it is testing, which makes it a lot easier to find the test that fails when you run your unit tests and get some red Fs in your field of dots.

Tests can be hard to real though, especially (but not exclusively) when testing legacy code. You may have lots of mocks to configure, for instance, or you may have several similar tests that are testing specific situations, edge cases or bugs you found along the way that you wanted to test before you them. Even when you wrote the tests yourself, in 6 months you may not realize what the context was when you wrote the test. Or you might have another developer join the team that is not aware of the context.

Documentation is important. It lowers the bus factor, makes it easier to on-board new developers (or temporary external developers, waves) and makes you think about your own code in a different way. We're (getting?) used to documenting our software, but why not document our tests by giving it a bit more context?

It fixed a bug

Earlier this week I was writing tests for the code I had just written. I usually write empty test methods first for every situation I want to test, and then fill them up one by one. As I came to the last empty test method I looked at the situation I wanted to test. I implemented the test as I thought I had meant it based on the name of the method. Then I started adding docblocks to give the tests a bit more context. As I was writing the docblock for the last method I paused: Something was wrong. The thing I was describing was not actually the thing I was testing. Looking closer at the test, it made no sense. Everything I tested in this method had been tested in other methods.

I ended up rewriting the test to actually cover the situation I had wanted to test, and tweeted:

I started adding docblocks above test methods to describe what I'm testing. I just caught myself writing a nonsensical test that way. WIN. (@skoop)

What to document?

The way I write the docblocks is that I describe, in actual human understandable language, which situation the test covers. For instance, for one of the above examples:

 * This test checks that the happy flow is correctly handled. 
 * If the queue returns the right data according to our
 * specifications, it should return a 200 response.

This will give you a lot of information about the test. But this one is for the standard happy flow, so it's still short. Let's have a look at another one.

 * This test checks the failure flow: Matching transactions 
 * fails. We also test whether database transactions are
 * used successfully: We should still commit the transaction
 * in this scenario

Here I don't just explain the flow I'm testing, but I also explain some additional things we test in this specific test: Many developers would assume that in a failure scenario the database transaction should be rolled back, but in this specific case, it fails to match information, but that is an expected outcome, so we should still commit the database transaction.

Assumptions are... well, you know the drill. I realize that as a developer I make assumptions all the time, but if I can minimize the assumptions I (or other developers) make with only a small bit of effort by documenting those details, that's a WIN in my book.

DDT: Docblock Driven Testing

So these days as I start writing my tests, I still create the empty test methods first, but they are now immediately accompanied by the docblocks, describing in a bit more detail which situation the method is going to be testing. That helps me make sure I don't accidentally miss any possible scenario, or accidentally write a test I had completely meant to be different.

PHPNW: Thank You

The past ten years, the PHP NorthWest conference in Manchester has had a huge impact on the Manchester PHP scene, but also on the rest of Europe (and perhaps the world). Last weekend during the closing of the conference, Jeremy Coates announced that PHPNW conference is going on a hiatus. They're not saying they're quitting, but for now, there will be no more PHPNW conference. A sad moment for sure, but I'm proud of all those involved in organizing PHPNW for 10 years.

Inspiration as a developer

PHPNW has ensured a constant inspiration for me as a developer. They've always had a nice and varried schedule both expanding on existing topics and bringing new topics that are interesting to developers. I've been incredibly inspired by many talks at PHPNW, for instance the keynote by Lorna Mitchell and Ivo Jansch and the keynote by Meri Williams.

Inspiration as an organizer

A little and perhaps unknown fact: When the Dutch PHP Usergroup merged with PHPBelgium to form PHPBenelux and we started considering organizing a conference, we contacted Jeremy and Priscilla about this. They were kind enough to give us a boatload of information about organizing a conference, ranging from how to pick the right schedule to how to try and get sponsors. Their help was invaluable in this early process of organizing a conference. Also, how PHPNW was set up, the atmosphere it had, was a huge inspiration for the early PHPBenelux Conference.

Friends, so many friends

I have met up with so many old friends and made so many new friends at PHPNW Conference. I couldn't list them all even if I wanted to, but for instance Lorna, Jenny, Jeremy, Priscilla, Mark, Matt, Kat, Mike and Rick. I've had countless conversations and discussions with people I know, people I did not know yet or people who were close friends already. PHPNW was also the conference where I finally met Khayrattee, who came over from Mauritius. That is one memory I will never forget.

A big thank you

So this is a big thank you to everyone involved in organizing PHPNW Conference those ten amazing years. You have given me and a lot of other people a lot of fun, opportunities and many lessons learned. You are an inspiration to me and I'm sure to countless others. I hope to see you at some point, somewhere in the future. Thank you.

Why I will pay more attention to game nights

WeCamp was last week and a lot was learned, and a lot of fun was had. Most lessons learned were good and nice, but I'm going to state here and now that I learned about something I should've known before WeCamp, but that I did not pay attention to: Which games to bring to game night.

Game night

Game night at WeCamp is all about leaving your electronics behind: It is meant as a moment of leaving work behind and play board- and card games together with other attendees. It is for having fun and relaxing.

Cards against humanity

During the first year of WeCamp, Cards Against Humanity was very popular in the PHP community at events. As such, I brought it to WeCamp. As the game night progressed, however, I started wondering whether this was a good choice. A majority of attendees were playing it, causing some people to be left out. The game can be quite offensive and not everyone enjoys playing such a potentially offensive game (even if all offensiveness is done in an atmosphere of joking, and is not meant seriously). After the first WeCamp I decided not to bring the game anymore. I wouldn't block it from being brought by other people, but would myself actively pursue playing other games.

Making the same mistake all over again

Earlier this year I received a new game I backed through Kickstarter: Secret Hitler. While the name feels offensive, the gameplay looked really good, and within the right context this should not be a problem. Excited as I was about this new game, I packed it into the box of games to bring to WeCamp, not thinking about the effects it might have on others. I was just really excited about this fun new party game I had purchased.

Context is important

What I had not considered is that with games like Cards Against Humanity and Secret Hitler, the context is very important. When played among friends it is clear to everyone that the game is not serious, that everyone is just having fun. When playing Cards Against Humanity, the purpose is to make jokes as offensive as possible without actually meaning offense. When playing Secret Hitler it is clear that nobody actually supports Hitler, that accusing someone of being a fascist is done jokingly and not serious.

However, when at an event such as a conference, or WeCamp, the context is different. While you may consider people friends, they are not good friends. And even when people are not playing the game, they may still be confronted with terms such as Hitler or fascist. I did not consider this when packing the game or setting it up, but even the usage of these terms, in whichever context, can be highly offensive to a lot of people.

Both during WeCamp and in the evaluation questionnaire we have received several comments about the fact that Secret Hitler was being played. This is what made me realize that I made a mistake. It was a big mistake in judgement on which games I could bring to the island, and for that I apologize to anyone who was offended by that.

There is so much to play

I have a huge stack of games that I could bring to any event, including a lot of fun (party) games. At WeCamp, for instance, Dixit was a very popular choice. We had a lot of fun playing it. Another popular choice was Bang! The Dice Game, Saboteur and 7 Wonders were also very popular. Other good choices could have been Exploding Kittens or the game I got recommended by friends: Bunny Bunny Moose Moose.

Considering it now, one of my other favorite games, the historically accurate World War II boardgame Escape From Colditz would be another good example of a game I should not bring to an event like WeCamp.


Looking back at WeCamp, I can say: Today I Learned that even though games may be associated with fun and may seem innocent, picking the right games for the right context is still important. I made a mistake by bringing Secret Hitler to WeCamp, and will give more thought to my choice of games for any future event that I attend.

Should we all stop playing these games? No. But we should remember that there is a time and a place for them. And this was not it.

Customizing Sculpin: Highlight image and Facebook

Over the past months I've been slowly customizing my Sculpin installation for this blog to fit my own liking a bit more. I've added a bit more styling including a beautiful background image and a transparent white background for the content column. Today I wanted to add a bit more. Two things specifically:

  • I wanted to control a bit more about how my blogposts are displayed when they are shared on Facebook
  • I wanted to have an optional image at the top of blogposts to make them look a bit better

It turns out this was actually quite easy, so here's a short description of what I did to make it work.


A quick search gave me the exact Facebook documentation I needed for setting up basic markup to make my site look better when shared on facebook. It basically means adding a couple of tags to the header of my HTML. Now that is easy! So in my source/_views/post.html I've added some lines to the head_meta block, which is the block in the layout that contains meta-data. I found this quite fitting.

    <meta property="og:url" content="{{ site.url }}{{ page.url }}" />
    <meta property="og:type" content="article" />
    <meta property="og:title" content="{{ page.title }}" />
    {% if %}
    <meta property="og:description" content="{{ }}" />
    {% else %}
    <meta property="og:description" content="{{ page.blocks.content|striptags|slice(0, 255) }}..." />
    {% endif %}
    {% if %}
    <meta property="og:image" content="{{ site.url }}{{ }}" />
    {% endif %}

Most of this seems pretty basic: I set the URL of the current article, I set the title to the title of the current article, the type is article (according to the Facebook documentation if you leave this out the default is website, which seems like an incorrect description of a blogpost). The description and highlight image meant I had to extend the standard blogpost format for the markdown file a bit more, I'll get back to that in a minute. But as you can see, I only add an image if I've set a highlight image, and I add a basic description unless a custom summary has been set in the blogpost.

Extending the Sculpin frontmatter

While I could just use a basic summary based on the blogpost and leave out the image, I wanted to have the flexibility to customize this a bit more. Luckily Sculpin allows you to extend the markdown frontmatter with your own custom tags. Basically, any tag (or hierarchy of tags) you add to the frontmatter in your blogpost markdown file automatically ends up in your data structure in the template. So now I can simple add some stuff to my blogpost, and I can use it in my template:

    highlight_image: /_posts/images/powertools.jpg
        name: Dorli Photography
    summary: I've customized my Sculpin a bit more to fit what I want with the blog.

As you can see, if I use a deeper hierarchy, I can access that by concatenating with dots, for instance the I use in the template comes from the above information.

Highlight image

Since I have a highlight image for Facebook anyway, I could actually use it to make my site look a bit nicer as well. So let's add the (optional) highlight image to the top of the blogpost as well. Since my (default, I think?) Sculpin template is split up into two templates, this required change in two places:

  • source/_layouts/default.html
  • source/_views/post.html

The first change is in the default layout: I need to add a block on top of the row that contains the blogpost to allow me to add custom HTML in my post template. This is a pretty simple task:

{% block topbanner %}{% endblock %}

The block will not contain anything by default, only if it gets overwritten by subtemplates. In our case, the template for the blogpost.

{% block topbanner %}
{% if %}
<div class="row-fluid">
    <div class="span12">
        <img src="{{ site.url }}{{ }}" style="width:100%" />
{% endif %}
{% endblock %}

In the source/_views/post.html I overwrite the block and add some content, but only if I've actually set a highlight_image for the blogpost. This ensures I can also blog without a highlight image, but also keeps backwards compatibility for the years and years of old blogposts that do not have a highlight image.

If the image is set, I simply add a new row-fluid with the image in it. Thanks to @jaspernbrouwer for helping me with the HTML here, I initially placed the HTML in the wrong place in the layout file. This will now add the highlight image at the top if it is present.

Credits where credits are due

Of course, if I use images of other people, I want to credit them. So I've added a bit of code to the sidebar as well to do exactly that:

{% if and %}
    Image by
        {% if %}
        <a href="{{ }}">
        {% endif %}
            {{ }}
        {% if %}
        {% endif %}
    {% endif %}

I think this could is pretty self-explanatory: If there is an image and the credits are also set, add the credits to the sidebar. If I've also set a URL for the credits, make the name a link.

The result is what you're looking at right now.

Company and team culture

I talk to a lot of companies and work at quite a few companies as contractor or consultant. Quite often I hear management complain about how hard it is to get good developers, and also that developers leave again after a while.

Now, there are many reasons why it can be hard to find the right developers. There are also many reasons why developers leave. One thing I've found though is that management does not understand developers, and does not understand what developers are looking for. And while in a lot of companies software is the main way to make money or at least supports the main source of income in quite an important way, the software and the developers who make it are not valued correctly.

For this article I want to focus on an important part of acquiring and keeping developers: Company and team culture.


Let's first have a look at what culture is in the context of companies and teams. Culture is about the environment you create within your company and within the development team(s). It is about how comfortable a developer feels while doing their job. In the end, a developer delivers their best work in a workplace where they feel comfortable, where they feel trusted, where they feel respected.

In the past couple of years I've talked to and worked for companies that have bad (or no) developer culture and I've worked for organizations that have invested a lot in building a great culture. But even the best of those organizations had some weird problems, and the worst of those companies had some good aspects. I want to focus on a couple of these things to illustrate what you can (or should not) do to build a good culture.


First and foremost, one thing that I've seen go wrong (and right) in organizations is trust in developers. To create a good culture for developers, it should be clear that management trusts the developer(s) to create a good product. You hire developers because you do not have the expertise, so trust the judgement of developers because of that expertise. Even if you have a background in development and know what you're talking about, if your job is not actually developing software, then trust the judgement of those that do that. It is OK to challenge developers, but make sure that it doesn't turn into "I'd do it like this if I were you". Offer a suggestion instead of telling them what to do: "What about this other approach?" instead of "I think this other approach is better".

Several of the companies I've worked for in the past years understand this. They give full trust in the judgement of developers, even to the point that the development teams are completely autonomous. Some organizations even give trust beyond development decisions to their developers, trusting that they will make the right decisions to build good products for customers but also keep their own team running and profitable.

In other organizations I've seen even relatively simple things like getting a big screen for a CI/status dashboard or a new external keyboard for a laptop to be hard because management did not trust developers enough to just accept this was part of a good work place.

Trust your developers to make the right decisions for your organization from a technical point of view like they trust you to make the right decisions from a business point of view. This is a great basis for a good culture.

No blame

Mistakes are made in every organization, by everyone who works. Making a mistake that ends up in production can of course have a huge impact on your organization, but rest assured that it has a huge impact on the developer that caused the error. Trying to immediately blame people is counterproductive though.

The thing is: Quality is not an effort that belongs solely with the developer. Developers are obviously the first people that need to focus on making sure their code adheres to your quality guidelines. They need to test their work to the point where they are confident that their code is working as it should, but the tester should not be the last person in the organization to test code before it goes into production. There should be processes in place for quality assurance. Functionality should be tested also by non-developers (because they look at the software differently), and preferably there should be unit and functional tests that are run automatically on any changes introduced in the code. If possible, there should be a dedicated professional tester. Within smaller organizations this is harder, but there should at least be one non-developer that tests the changes and makes sure things work as they should.

Having said all this, some things may still go wrong. But instead of blaming the person introducing the issue, look at how to prevent the problem in the future. This is a much more constructive approach and puts focus on team responsibility instead of individual mistakes. Accept mistakes as part of the process, and work to minimize them.

"Better to trust the man who is frequently in error than the one who is never in doubt." --Eric Sevareid


A team is a team. It is a group of people that have to work together to deliver good software. If you've compiled a team of good developers, testers, designers and everyone else you may need to deliver the product that you're working on, make sure that they keep being a team.

I've worked for an organization some time ago that had their developer culture pretty well set up. There was trust, there were autonomous teams, there was a high focus on quality, there was little blame. It was a great team to be a part of. Because there were several teams, it was deemed important to have a regular meeting with all the teams together to keep them aligned with eachother. A great idea! Sharing knowledge, experience and also decisions between teams is a great way to ensure good, consistent quality in software. The organization deemed the meeting important, yet not important enough to sacrifice development time for the meeting. Because of this, the meeting was scheduled during lunchtime.

Now, working for this company was a joy, and I learned a lot, but it was also quite intense. My head needed the lunch break to get some rest, to relax so that I could take the afternoon without burning out. This may have been a sign that we (or at least I) weren't doing a sustainable pace, but let's keep that out of scope for now. I really needed my lunch to relax. I notified the right people in the organization about this, and I was informed that while the meeting was important, it wasn't required, so I could skip it if I wanted. Over time, however, it turned out that during those meetings quite a few important decisions were either taken or communicated that I never picked up on. This started weighing on my mind: should I start attending these meetings knowing I would be burned out by the end of the workday? I decided that clearly these meetings were very important, so spoke up to the right people again to ask whether the meeting could be moved an hour later, so I could attend. Again, I got a "no". Despite important decisions and announcements, the meeting was not deemed required, so it was fine. And I guess for them it was fine. For me, however, it felt like I was not an important part of the team. Not important enough for being included in these decisions, in hearing other announcements. Given my mental health was already under some pressure due to the intensity of this specific project, this really weighed on me.

Once you've built a team of people, try to take any measure necessary to keep everyone on board. Consider all the input that you get, especially when you get it several times, and try to make it work for everyone involved. The last thing you want is that valuable people feel left out or unheard. Make sure to create an environment where everyone feels included.


Another thing that is very important is that you communicate clearly and openly about goals. Only when developers have a good idea of what the goals are for a project will they be able to take the right decisions, technical and otherwise, to create the best solution. I have experienced on several occassions that (project) management only communicated a partial context for a project. The developers then took decisions and set up their solution based on that input. Halfway through the project, the missing part of the context was then communicated, causing those projects to all of a sudden having to make a sharp turn or even a U-turn in terms of approach. At the end of the project (or even before it ends) it's usually the developers that are blamed (see above about blame) for a project either not being delivered on time or containing too many bugs, while often these can be prevented by creating a clear context.


Communication is not just about goals and context, it is also about expressing emotion. Whether that emotion is gratitude, happiness, disappointment or anger, it is usually better to communicate than not to communicate. Of course the communication should always happen with respect for the other party, not as a one-way communication, but being able to communicate about any emotion related to the project gives the other party a clear idea of what is going on.

From the developer point of view it is extremely important to hear about what makes customers (or managers) happy, but also what they don't like. This may sound obvious, but I've found that in a work environment emotions are often hidden, causing a gap between developers and those judging a project. The gap will grow bigger and bigger if nobody states how they feel. Even successful projects and teams can start to feel bad for a developer if they get no feedback on their work, and they'll feel alone and perhaps start looking around for another job where they get the feedback they need. Same goes for negative (constructive) feedback. If you know you messed up but nobody says anything, you'll get the feeling that people are avoiding you. Given most developers look for a place where they feel welcome, where they feel at home, things like this may cause them to look for another job.


This may seem obvious, but people like to be treated with respect. Developers are no different. And yet, I've seen so many organizations that do not treat their developers with respect.

Now even the best empath may have a hard time being empathetic towards a developer, simply because developers are different. They can be nerdy, geeky, have completely different thought patterns and their minds may simply work in a different way from yours. Many developers I know will care less about getting a raise and more about, for instance, getting a good standing desk or an extra monitor for their workplace. Some developers really need a good mechanical keyboard for their work and care more about that than an extra monitor.

But even if you can't empathize with them, if you don't understand them, they do want to be treated with respect. If you don't listen to their needs, if you don't respect them for their work, there's a good chance they'll move on. They'll find a better place. So please treat developers like you treat any other human (assuming you treat all humans alike, and all humans with respect).

Now apply this

It is not easy to create a good company or team culture. It should be really easy though. The key is to respect and trust the people you have working for you to do the job they were hired to do. Developers coming in for an interview have ways of figuring out if a team or company feels right. If they feel you've got your stuff sorted, they'll join your company. And if it turns out you actually got your stuff sorted, they'll stick around. Imagine all the money you don't have to constantly invest into recruiters and recruitment when people stay with you for a long time... if you invest part of that money into your developers, into the team and company culture, you'll have saved yourself a lot of money.

Now people leave. This is a fact. Especially developers will sometimes simply need a new challenge. But even a developer that is leaving can give you good insights into how to further improve your company. Listen to those who leave, and learn from them.

Good luck building that awesome team and company culture. You can do it.

A list of podcasts

I had sitting in travel not being able to do anything. Listening to music can help, but can end up also being frustrating. While I was working at Schiphol last year I got pointed to podcasts. Since then I've been really getting into listening to podcasts on my daily commute and it's been making the trip a lot more fun... and useful.

Here is a random collection of podcasts that I've been listening to in the past year and a half, on many different subjects. My main problem right now is that it's so many that I'm way behind on the episodes of most of these. Ah well, it's still quite good content.


The following podcasts are related to development or design of software.


Chris "Grumpy Programmer" Hartjes and Ed "OSMI" Finkler talk about software development and related topics in this funny and interesting podcast. They're already up to episode 92 by the time of this writing.

Revision Path

I found this one when Liz Naramore retweeted one of their tweets. The podcast "focuses on showcasing some of the best black graphic designers, web designers and web developers", and it is extremely interesting to listen to the stories that are being told. If you're a backend developer, you'll learn a lot about the design process as well.

The Agile Path

John Le Drew started this very ambitious podcast in which he creates "audio documentaries on the issues facing organisations as they move towards more agile ways of working." The first two episodes are out already and are extremely good. John has a very special style of storytelling that makes it very pleasant to listen to.

Sound of Symfony

If you like Symfony or you want to learn more about Symfony, this is one of the good podcasts to listen to. 4 seasoned users of Symfony discuss new developments in the Symfony community and the Symfony framework.

That Podcast

Beau Simensen and Dave Marshall discuss, well, their life. Since they're both software developers, this includes but is not limited to software development. I really like the combination of tech and non-tech subjects in this podcast, and the laidback way of presenting.

Voices of the ElePHPant

Of course if you're listing podcasts on software development, you can not go without listing Cal's Voices of the elePHPant. Every episode Cal interviews a member of the PHP community, and every episode you learn something. I would like to highlight the recent episode with Sara Golemon which, as you would expect with Sara, is hilarious.

Justice, true crime, wrongful convictions etc


I got recommended Serial after the first season, which talks about the wrongful conviction of Adnan Syed, which was a really good and insightful season with an unfortunately relatively disappointing conclusion, mostly because of the fact that it did not give any closure on the story. The second season was also quite good, although a completely different story.


The first season of Undisclosed left off where Serial stopped after their first season, and dug way deeper into the loose ends of the case of Adnan Syed. It may sound slightly less professionally produced at the start, but they dig a whole lot deeper than Serial. And they dig similarly deep with their new seasons. Extremely interesting to hear, and very educating for those of us not in the USA.

Suspect Convictions

Another interesting podcast on an actual murder case that has a lot of loose ends is Suspect Convictions. Again, another podcast that seems to dig deeper than the average journalists these days dig into stories, and which teaches a lot about the USA and the US policing and justice system.

Other non-tech podcasts


In this podcast a very detailed analysis is done of Kendrick Lamar and specifically his album To Pimp A Butterfly. Every episode a new track of this instant classic album is analysed. Aside from understanding more about the music and the lyrics, we learn a lot about Kendrick Lamar and life in Compton.

Inner Pod

This one is perhaps related to tech in that is contains stories by people from the tech community, but it is about mental health. The guests on this podcast tell about their personal stories of mental health issues and how they have handled it.

Jerks Talk Games

Chris and Gary talk computer games. And Chris and Gary are PHP developers. But mostly, this is about computer games. And it is a lot of fun to listen to.

Dutch podcasts

The following podcasts are in Dutch, but I find them extremely interesting.

De Appels En Peren Show

Wietse and Reinier talk tech. Sometimes specific new technologies, but also about the impact of technology on our society, or ethical discussions around new technology. Entertaining and informative.


Glitch podcast is another podcast about new tech, but from a slightly different angle than De Appels En Peren Show (even if both podcasts share one host, Reinier). Unfortunately Glitch has not published an episode in months, and I'm not sure whether this podcast is dead or alive, it is a good listen. Just listen back to the previous episodes.

Michiel Veenstra@Made With Opinion

OK, so this is a nerdshow for those who like tech and/or radio. In the 5 episodes that are out so far Michiel interviews Domien Verschuuren and Adam Curry, but also talks about Meneer Aart, digs into privacy on the Internet and looks at audio design.

Wilde Haren De Podcast

Vincent Patty (Jiggy Djé) and Kees van den Assem (Spacekees) talk to people. I love this concept. No format, no specific subject, no real deadline. Just a recording of people talking to eachother about topics they like and the world in general.

The idempotent command

The great thing about the server architectures we have these days is that everything is scalable (if you set it up correctly). The hard thing about the server architectures we have these days is that everything is scalable (if you set it up correctly). Yeah, I know.

One of the things you may run into these days (and that I had to solve this week) is that these days we provision all servers similarly (or according to their role). This may also mean that you provision several of your servers to run the same cronjobs at the same time. However, some tasks may not be run multiple times, and especially not at the same time. In a symfony project I'm working on, I was tasked with making sure some of the cronjobs would only be run once, even if started on several servers at the same time.

Adding the locking

My initial idea was to add a locking system to all commands that had to be idempotent, but I felt this was a bad idea: Having to add similar code to several different classes did not really make sense to me.

While looking for a different option by searching for Symfony Command classes and events I came by this blogpost by Matthias Noback. While his specific use case in that blogpost is different, it inspired me: I simply needed to use the events console.command and console.terminate. I would be able to hook into those events to initially set the lock and then on termination release the lock.

The lock library

The next step was to find the right type of locking. I looked around for libraries that could do locking. In that process I came by the very recently pushed symfony/lock. Unfortunately that was a bit too fresh for me to use. Eventually, I settled on arvenil/ninja-mutex, a nice and simple library that can do Mutex locks on a variety of backends. We went with the Redis backend for our locks.

Deciding when to lock

The thing is: I don't need all commands to do locking, I only need specific commands to add and release locks. My initial plan was to simply create an array of class names in the listener, but that did not feel right. This meant that every time we'd add a new command that needs locking, we'd have to update the listener.

Another option would be to keep track of a list in the configuration, but that similarly did not feel right.

I ended up going for an implementation with an interface. The interface, that I called IdempotentCommand, contains just a single method. The method that needs to be implemented is getIdentifier(): string, which would return the identifier used for the lock.

The listener

Time to write the listener. The listener needs to listen to two events:

  • console.command is the event triggered by starting a Symfony Command. This is where I need to create the lock.
  • console.terminate is the event triggered by a Symfony Command ending execution. This is where I need to release the lock.

The listener is pretty simple. It gets the MutexFabric class from arvenil/ninja-mutex as a constructor argument that it can use internally. It then implements two methods, one for the first event and one from the second event.

Creating the lock

public function onConsoleCommand(ConsoleCommandEvent $event)
    if ($this->shouldBeHandledIdempotently($event->getCommand())) {

private function shouldBeHandledIdempotently(Command $command)
    return $command instanceof IdempotentCommand;

private function acquireLock(string $name)
    $result = $this->mutexPool->get($name)->acquireLock(1000);
    if (false === $result) {
        throw new ProcessLocked('Process '.$name.' is locked and can not be executed');

    $this->acquired = true;

private function getLockName(string $commandName): string
    return 'command-'.$commandName;

The onConsoleCommand() method is linked to the console.command using a service tag:

- { name: kernel.event_listener, event: console.command, method: onConsoleCommand, priority: 1 }

First we check whether this command is required to be locked. If so, we try to acquire a lock. If we succeed, we keep track of that by setting a local property (yay, we are the actually executing process). The purpose of this property is to prevent a second (or third) process that is started to release the lock when it ends before the initial process is ended. If we can not acquire a lock we throw a ProcessLocked exception to quit execution immediately.

Releasing the lock

Once the main process has ended, it needs to release the lock. To do that, we have a second method in our listener class:

public function onConsoleTerminate(ConsoleTerminateEvent $event)
    if ($this->shouldBeHandledIdempotently($event->getCommand()) && $this->acquired === true) {

private function releaseLock(string $name)

Here we simply check whether this command is supposed to lock and whether the current process has acquired the lock. If so, it releases the lock. That's all.

Making things lock

Now the only step left is to find the right Command classes that need to be locked, and make them implement the IdempotentCommand interface I defined at the start. These are now automatically picked up by the listener to set and release a lock accordingly.

If I now start the same command twice at the same time, only one of the commands will actually run, the other one will be stopped by the exception and won't run at all.

A small extra lesson

During the process of building the second listener method, some weird things were happening. I would get an error at the end of the Command execution about the locks, and whatever I did it seemed the second listener was never triggered. After a lot of searching I found the error to be a single missing comma. I had accidentally typed:

- { name: kernel.event_listener, event: console.terminate method: onConsoleTerminate, priority: 1 }

This is still valid YAML, so Symfony did not complain about it, but because of the missing comma between console.terminate and method: it did not pick it up to be a listener. The devil is in the details, and it took me a while to figure this one out.

What is WeCamp all about?

Recently I got an email from someone who was interested in coming to WeCamp, but needed some more information to help convince their manager. I wrote a big email trying to describe WeCamp and our ideas of what WeCamp is (or should be).

Since this may actually be useful to more people that want to come to WeCamp but need to convince their manager, I'm posting that email here as well.

The idea behind WeCamp is that attendees will go through the whole process of a software development project. When the attendees come to the island they will be assigned in teams with 4 other people that they most probably not know (we try to split up people who work for the same company or live in the same region). Every team of 5 people will be assigned one of the coaches, and this coach will support the team in finding their own way in the project. These coaches are specifically NOT teachers, but instead people who will support the team, because we want the team to find their own way most of the time.

Once a team is formed and assigned a coach, they find a working place on the island, and they'll have to think of a project to work on. The team will have to think of a project themselves and decide on what is realistic to build given the limited amount of time (usually about 3 days of development). Once they've decided on their MVP and created their initial planning, they start working on their project.

Each team is free to choose a methodology for development. While we've so far seen no waterfall-style development, we've had several different methodologies from full scrum to kanban and variations on both because the team decided that was the best approach. One thing all teams have though: A central stand-up right before lunch where each team can share their progress, the problems they've run into and their lessons learned. In that way we try to get everyone to learn from everyone else. During the fifth day, each team will present their project as well as their lessons learned while doing the project.

To balance out all the hard work, we have some social events during the week. We have a game night (with board- and card games, no electronic games!), we have a BBQ and we have a "pirate game", an activity where a pirate comes to the island with some friends to have everyone do some assignments. When there's no special events at night, there's always the option for a drink with fellow attendees around the camp fire. Everyone sleeps in tents with beds in 'em, on the island. The price for the ticket is all-in: It includes all drinks and meals so attendees don't have to worry about anything during the 5 days on the island.

When we first started WeCamp 4 years ago, we meant to start a technical event. We had a lot of experience with conferences as an inspiring place for new tech, but felt we never had time to actually play with that new tech. That's what we initially aimed for with WeCamp: A place to actually play around with new tech. It quickly turned out though that all the tech is cool, but WeCamp was about more than just tech. It was about personal development. About learning a lot of soft skills next to all the tech: communication, teamwork, planning, making decisions, presenting.

The coaches will have private conversations with all team members, and together they will create a personal development plan with attendees. Some time after WeCamp coaches follow up with their team members to see if the goals set in the personal development plan were reached, or at least are being pursued.

To Exception or not to Exception

I recently found myself in a discussion on whether or not exceptions could be used to control program flow in software (specifically in PHP applications). That triggered me to post a tweet:

Exceptions should not be used to control expected application flow. Discuss.... @skoop

This triggered quite a bit of discussion, which gave me a lot of input on this topic. I want to thank everyone who joined that discussion for their input, which was really valuable. In this blogpost I'll do a summary of the different arguments in the discussion, and give my opinion on this.

What is program flow?

First of all, what exactly do I mean with program flow and using exceptions to control program flow. The reason for the discussion was an exception thrown in a persistence layer in the situation that there were no results in a findBy* method. This is a slightly different situation from for instance errors with database connections or API connections, things that can be expected but are not meant to be happening. When you do a findBy*, you're effectively searching, meaning 0 results would be a valid situation. This also was reflected in the discussion.

@mvriel @skoop @rdohms Find implie Search. Search implies zero results is a valid output. Excepting in a Find-er seems odd to me.Make it a Get-er or wrap in Option @n0x13

This triggered a whole discussion on whether for instance findById is actually searching and whether it would be a valid use case that this would return 0 results. For instance:

@n0x13 @skoop @rdohms Finding one item specifically by ID implies that it exists; non-existance is equal to an error 400. @mvriel

Why exceptions?

A great definition of what an exception is and when it should be using was given by Chris:

@thomas_shone @skoop Exceptions are only where the code throwing the exception cannot deal with it. Calling code can always expect the exception and cope! @choult

I quite agree with this definition of exceptions. If your code can not deal with a certain situation anymore (such as a missing connection to the database, or an error 500 response from an API) then it should throw an exception. Basically, when something should be there and is not there. In all other situations, your program flow should handle "errors" by itself.

What is the intent of your code?

I had not really expected this when I first asked the question, but eventually I think the discussion stopped being about exceptions, and started being about naming and intent. Now, we all know there are two things that are extremely hard in software programming:

  • Cache invalidation
  • Naming things
  • Off-by-one errors

Let us focus on the middle one: Naming things.

Naming things is hard. It is extremely hard. It is so hard people do talks about the subject. But in essence, naming things is easy. Names should be clear, descriptive and describe the intent of the code. Yeah, that sounds easy, but once you start to think of what the right name is, it gets harder. Actually finding the right name is extremely hard.

So, let's go back to the example of findById(). Given several tweets in the discussion, different people interpret this method and its intent differently. There's basically two interpretations:

  • find implies search, which means you're going to search for a record with the given ID. When you search, one of the options would be that no results are found
  • ById implies that you're asking for a specific record, because ID is usually a unique key. If you know that key, then the record must exist. If it does not exist, this is an exceptional situation

And both interpretations are valid. Which basically means the naming is off. Jaap has a good solution for that:

@mvriel @skoop @rdohms Find can return null, getbyid should throw an exception @jvotterdijk

I like this idea; when you search (represented in this tweet by find) 0 results is a valid situation, but when you getById() you expect it to be there, so that may result in an exception if it is not there. Nicolas later confirms this as well:

@skoop Agreed, exceptions shouldn't be used for flow control. Although valid when ex: has(): bool, get(): Object (throws Exception) @nicholasruunu

So, naming things...

... is still very hard. But it is extremely important. So please think hard before you name things, and avoid ambiguity. Always choose names that are clear and that communicate the intent of the code you are writing. Because it isn't wrong to throw an exception when you getById() and you find nothing, but when you searchById() it is OK to get an empty result which should not result in an exception.

Sculpin and Docker

I've been running this blog on Sculpin for quite a while now, and I'm quite happy with how that works. We've been in a process of migrating some of our websites off a standard VPS towards a setup with Docker, Gitlab CI and Rancher. I've now migrated some websites, which is relatively easy, but most of those sites were dynamic PHP websites. Migrating a site that generates static HTML and running that is a slightly different thing. Here's how I ended up doing it.

The old setup

First, let me quickly describe my old setup. I had set up Sculpin on my webserver and put only the blogposts into a Git repository that was hosted on Github. After committing and pushing a new blogpost, I'd manually run a shell command that would generate the new static version:

ssh -t user@domain 'cd /var/www/vhosts/;git pull origin master;cd /var/www/vhosts/;php /var/www/vhosts/ generate --env=prod --url='

This worked, but would be a bit harder with a new setup. Besides, why would I want to do it manually when I could have it all be done automatically after pushing a new blogpost?

The new setup

First, let's have a look at the global setup that we have:

  • Gitlab for Git repository hosting, we're using Gitlab pipelines to build and push changes
  • We've got droplets on Digital Ocean which contains our Docker/Rancher setup
  • Our production setup is managed by Rancher

So once we push some changes to master (I mean: merge a merge request) a Gitlab pipeline is triggered that builds the Docker container, and pushes the new container to Rancher. All of a sudden, the website is updated.

Now that we've got that basic setup described, let's have a look at how I've set this up with Sculpin.

My initial attempt

In my initial attempt I started with the base Nginx container and started working from there.

FROM nginx

I started running into some issues with installing PHP, so I decided to approach it from the opposite side.

My second attempt

I started over by using the base PHP7 container

FROM php:7.0-cli

So, first things first: I'll need to install Nginx, because eventually I need to serve my static website to anyone wanting to visit my site.

RUN apt-get update
RUN apt-get install -y nginx

For installing Sculpin I'll need Git and the Zlib library, so I'll also install that.

RUN apt-get install -y git
RUN apt-get install -y zlib1g-dev && docker-php-ext-install zip

Now, let's install Composer so I can use that for installing Sculpin.

ADD /tmp/composer-installer.php
RUN php /tmp/composer-installer.php --install-dir=/usr/local/bin --filename=composer && \
    rm /tmp/composer-installer.php

OK, all prerequisites are installed, let's install Sculpin.

RUN git clone /usr/lib/sculpin && \
    cd /usr/lib/sculpin && \
    composer install && \
    ln -s /usr/lib/sculpin/bin/sculpin /usr/local/bin/sculpin

Now I have a basic Sculpin install in /usr/lib/sculpin. I now want to customize that installation with my own custom information. I've structured my Git repository in such a way that I can easily copy my custom configuration into this base installation. My repository layout is:


app/ contains a config/ directory that contains the Sculpin configuration files (sculpin_kernel.yml which contains the URL structure and sculpin_site.yml which contains some basic site information, my Google Analytics ID and the Disqus configuration).

posts/ contains all my blogposts. This is basically what I imported from my old Git repository. All the blogposts are in here with any images that may be needed for the blogposts.

source/ is my own source directory for Sculpin. It contains my theme and custom pages. As we'll see later, this is also where the contents of posts/ will end up being placed, but I wanted the blogposts to be more easily accessible, so I've seperated the posts/ directory.

So, given that structure, I can now copy the contents of those directories to my container.

COPY app /usr/lib/sculpin/app
COPY source /usr/lib/sculpin/source
COPY posts /usr/lib/sculpin/source/_posts

I copy the app/ directory to the Sculpin installation, I copy source/ to the Sculpin directory, and now I copy the posts to the _posts/ to the source/ directory. Now I've got everything I need to generate the static website using Sculpin.

RUN cd /usr/lib/sculpin && bin/sculpin generate --env=prod --url=

This will call Sculpin to generate the new static version of my blog. The new version is generated in the output_prod/ directory in my Sculpin installation. Of course, this is not the Nginx document root, so I need to make sure I can expose the static site using Nginx.

RUN rm -rf /var/www/html
RUN ln -s /usr/lib/sculpin/output_prod /var/www/html

Firstly, I remove the default Nginx document root. After that, I create a symlink to the output_prod/ directory. Now I can serve my static site. The only thing that is left is to ensure Nginx gets started.

CMD ["nginx", "-g", "daemon off;"]

This starts Nginx and makes sure the site is now being served. Everything is up and running!


I need to give some credits of course, because I have used some sources for inspiration. First of all, I used the gitlab runner sculpin to check some of steps in the Dockerfile in my second attempt. Also, my standard "HELP! IT NO WORK!" person Mike gave me some insights in to how to solve some of the problems I encountered along the way.