Make it a marathon, not a sprint

The year was 2002; I was working as technical support for an internet provider. It wasn’t always busy, and although playing MUD and hanging out on IRC were some of the ways I and my colleagues used to pass time, eventually, I started blogging, and that is when things started to change. Looking at what other blogs had, I started to learn HTML and CSS to have a nice-looking blog. Sometime later I started building free templates for other bloggers, and that led me to win a layout competition organized by the blog platform we were using. The prize was a book. I chose a PHP book, and that’s how I first learned to code in PHP.

Fast-forward to 2022, twenty years later, writing is now my main occupation, but PHP has always taken a very important spot in my life and my career. It is the language that formed me as a developer, it is the language in which I built code that paid my bills for a long time, and it is the language in which I built many fun projects and learned about other technologies. It is still my language of choice for demos and side projects. More importantly, PHP is the community that embraced me and supported me throughout my career. 

And it wasn’t easy, you know? I thought of giving up many times. At first, because learning how to code is hard – we often forget that. It was even harder back then, with little to no resources in Portuguese. The books were always outdated, and the overall developer community wasn’t so inclusive. But the hardest part was actually making money. I’ve been through times when I was so broke I didn’t have money to take the bus to attend classes at the university. Meanwhile, some of the kids (the rich ones) who attended uni with me already had cars, didn’t have to work, and some were even enrolled in two graduations at the same time. It felt incredibly unfair at that time, but the show must go on – so I kept going.

From time to time, it is important to think about what we have accomplished, because memories fade easily and we often forget how hard it was at the beginning. It is also very important to let people who are just starting know that it wasn’t a breeze, but they will get through it. If I were to meet the Erika from 20 years ago, here’s some advice I would give her:

  • It is hard to get good at anything. But there will be a tipping point when you are ready to pay the price – which is not much about money (although some things will require it), but paid with time and dedication. Money can’t buy this.
  • For a time that will feel very long at first, you will feel like you don’t have any progress, and you will feel frustrated. You may even think of giving up. But you absolutely must trust the process. Once you are past the “wave breaking point”, you will be able to move faster and actually enjoy the process. 
  • Meaningful change requires long-term commitment. Long-term commitment is not a sprint, it is a marathon; keep your pace, and focus on consistency. 

Some of you may know that I like to lift weights. Although this is not something completely new in my life, and I have been going to the gym inconsistently for many years, it was only in 2022 that I decided to take it more seriously, with the ultimate goal of getting really strong. This has been an incredibly humbling experience because the progress is so slow! For many months, there was nothing to be seen. No visible change. And it felt a lot like learning to code, the frustration, the feeling that others had it easier than me, that I wasn’t good enough. Like walking miles and getting nowhere.

But I kept going, focusing on consistency, with patience, and dedication. Now I can clearly see the difference, I am in fact lifting heavier weights than months ago, and the practice is consolidated in my routine, something I look forward to when I wake up. I think that’s the most important bit, actually – that consistency pays dividends, and you gotta play the long game.

At this time of the year, it is natural that we think about what we’ve accomplished and whether or not we reached our goals for the year. I want to ask you to please extend to yourself the same graciousness and generosity you give to others; sometimes we can’t see it, but there is always progress. At the very least, you learned a few different ways that won’t work for you. Rest, recharge, and think about what you want to accomplish in 2023. And remember to make it a marathon, not a sprint.

Making PHP the language you want it to be

Have you ever had an idea about how to improve PHP? Or maybe you yearn for a feature that another language has, that hasn’t made it into PHP yet?

The great news is that PHP is improving with every release. One day the PHP feature you wish for might make it into the language.

If you’re really keen on an idea, then you can suggest it gets implemented via PHP’s RFC process.

The RFC process

The RFC process for PHP features works along these lines:

  • You have an idea.
  • You create an RFC document that explains your idea.
  • Your RFC is discussed by those on the internals mailing list.
  • A vote is held.
  • If the vote passes (two-thirds have to say yes), the feature is created and eventually released.

The RFC process is great in that it allows anyone to contribute and set the direction of the language.
However, the time from idea to release takes months to years. Given time to discuss and vote, feature freezes, etc., if all the stars align perfectly you might be able to go from the initial idea to release in 6 months.

In most cases, this time frame will be a lot longer. More complex suggestions often take a few iterations of the RFC process before they finally get accepted. It’s not unheard of for features to take several years from idea to being part of the language.

There is another way…

The good news is that, for certain features, there is a quicker way to add them to the language.

An example

Suppose you have a method on a class, and you want to make sure that it is only called from a certain class.

E.g. you might want to limit a Person object’s __construct method to only be called from PersonFactory.

PHP’s visibility modifiers (public, protected and private) are not fine-grained enough for this.

Let’s just be friends

Some programming languages have the concept of “Friends”.

A method states that they are “friends” with another class. The method can only be called from the class it is friends with.

In our example, we would state that the Person‘s __construct method has a friend: PersonFactory.
This would mean that only code in the PersonFactory class is allowed to call new Person.

How do we do this in PHP without going through the RFC process?

Advanced static analysis tools like PHPStan and Psalm analyse your codebase. These tools have a set of rules they run over the codebase they are analysing. They report any violations of the rules.

Typically, you’d run static analysis tools on your codebase at the very least as part of your CI. Ideally, you’d run them as part of your workflow, before committing and pushing code.

As well as all the predefined rules provided by the static analysis tools, it is also possible to create custom rules. One such custom rule could enforce “friend” visibility.

Assuming we have a suitable custom rule to emulate friend functionality, we still need a way of telling the custom rule who is friends with whom. This is a great use-case for PHP attributes.

To add the friend functionality, two things are required:

  • A #[Friend] attribute that can be applied to a method.
  • A custom rule that enforces the friend functionality.

Back to our example, the code would look like this:

class Person
  public function __construct() {
    // Implementation

The friend relationship is enforced not by PHP itself, but by a static analysis tool running custom rules.

If you want to use this (and other new functionality) on your code, then look up PHP language extension library and the PHPStan extension.

Find out more about writing custom PHPStan rules.

The formula for new features

Attributes + static analysis = new language features

We can emulate new language features using static analysis custom rules. Where additional information is required, this is provided via PHP Attributes.

Instead of relying on new features being added to the PHP language itself via the RFC process, we run static analysers with the custom rules on the codebase to enforce new language features.


One major benefit of this approach is the speed new language features can be created. Once up to speed with writing custom rules, features like #Friend can be made in an afternoon.

There is more flexibility to tweak the behaviour of the feature, once it actually is in a codebase. Contrast this with the RFC process, where it is important to get the feature right the first time. There is an argument that all features that could be implemented via static analysis, should be, at least initially.
This allows it to get battle-tested in the real world and all the issues ironed out before it is finally added to the language via an RFC.


There are a couple of drawbacks:

Firstly you have to be running static analysis on your codebase.
I’d argue that you should be doing this anyway, as it is so valuable.
But if you’re creating libraries, there is no way to force your users to run static analysis.

Secondly, this technique is not suitable for all features, e.g. it is not possible to change the syntax of the language.

What language features do you want?

Static analysers, like Psalm and PHPStan, are up there with PHPUnit and Composer, as some of the greatest tools in the PHP ecosystem.

Hopefully, you’ve seen how static analysis tools running custom rules, can be used to create new PHP language features. What new language features would you like?

Three developer super powers you may not realise that you have

If you’re reading this, then I assume you do software development of some sort. You write code. You “make sand think” as I heard someone describe it.

If you write code then I believe you have some super powers. You may not know it. Or you may not believe it. But I know it, and I want you to believe it about yourself.

Because it’s easy to beat ourselves up as developers. We fail a lot. We have to learn new things every day. And we often see those who are excelling posting their success on social media, making us feel like we are imposters.

But we’re not imposters. Many look at us and think that we are doing magic.

Here’s my manifesto; my creed; my decree: If you write software, then you have three incredible traits:

  1. You are creative!
  2. You are curious!
  3. You – yes you – are extraordinary!

You are creative

The word “creative” is so often used to refer exclusively to visual arts or handicrafts. But creativity is SO much broader.

Creativity is how you make and invent and solve problems. It’s being brave enough to both throw away something that doesn’t work, and to risk showing what you have made to others.

We are all making things all day long. But I’m willing to bet that when you sit down (or stand up) to code you are engaging in tricky problem solving; or preparing to publish your work for the world to see; or thinking up a new way to achieve what you have been asked.

Even if you’re copying and pasting from Stack Overflow, or using GitHub CoPilot’s AI to help, you are still combining existing things to make something new. That’s creative too. That’s most creativity, in fact.

I bet that you are, actually, a ridiculously creative person who makes multiple new things every day that you work with code. You maybe just haven’t realised it yet.

That shell script you wrote was creative. That API endpoint you created was creative. That test case you added was creative. Even that mundane list of routes you made was creative. We are building the internet every single day. It might not feel like it, but every line of code has power to do something new.

So I’m here to tell you: You are creative! And it’s your first developer super power.

You are curious

PHP is moving fast. The web is moving fast. Tooling is moving fast. Servers and serverless technology are moving fast. I know, it’s a struggle to keep up sometimes.

But you want to keep up, right? Even just a little bit? There’s always something a little more to learn?

That’s because you have the power of curiosity!

I actually love the double-meaning of the word “curious”. I think it sums a lot of us up. It can both mean:

1) Inquisitive: Not content with what you already know. Maybe not even content with the status quo. You want to make things better. To learn that new technology or design pattern. To figure out how that thing can be quicker or better. Or what your manager actually wants when he asks for that extra column in the database, because maybe there’s a better way.

2) Strange: More on this below, but as coders we are all unique. Maybe you’ve embraced your geekiness. Or maybe you shun it. But we’re all interested in the machines that we work with, usually in a way that the general population aren’t. And that’s awesome, because in their eyes we are spell-casting.

Curiosity is how you embrace the world. Accepting its – and your own – strangeness; and at the same time, relentlessly questioning that strangeness.

If you have come to be coding then you have asked a thousand “how” and “why” questions. Every bug is a quest. Every Google search an opportunity to learn and grow.

You are curious – in both senses. It’s an amazing thing. Stay like it!

You are extraordinary

Extraordinaryness is almost certainly a part of how you are. Like with curiosity’s “strangeness” there is something about you that makes you interested in these boxes of electricity that can seemingly think, and in figuring out how to make them think.

Please take this the right way: You are not normal. Disassemble the word: “extra-ordinary” – literally outside, or beyond ordinary.

It may be a bind to be the person that always sets up the computer/mobile when you visit the parents/grandparents, or that gets your friend’s stupid printer working – again!

But they ask you to do these things because YOU CAN!

Understanding the foibles and quirks of these complex devices as well as you do puts you in, what, the 1%? I don’t know the number. But it’s WAY smaller than you think it is.

It’s possible that you live in a bubble of other people doing similar work and feel like everyone you know is a programmer. That imposter feeling probably creeps up on you frequently as a result. But know this: your ability to program is just unfathomable to most people.

And that’s because you are extra-ordinary, and you can do extraordinary things.

With great power…

So you are creative.
You are curious.
And you are extraordinary.

You’re amazing! Seriously, I’m pretty sure if you’re reading this then you have these powers – and powers they are.

You can make things. You can publish to the world. Your work can potentially effect change in business, society, politics, publishing, and more.

You know the cliché: With great power comes great responsibility. Wield your powers with care. They can harm as well as help.

I encourage you to code with generosity and kindness. To make the world better with what you do. Using that curiosity to question if we’re heading in the right direction, and, if not, hopefully being strong enough in the extraordinary person that you are to try to change that direction.

With that in mind: while we share these powers – I think… I hope! – we are also uniquely creative, curious and extraordinary. There is not another person in the world with the same traits, skills, experiences, successes, failures, knowledge and talents that you have.

You are uniquely equipped to be who you are and do what you do.

Isn’t that incredible?!

Now go forth, creative, curious, extraordinary person, into the world, and make good things!!

Keep your documentation up-to-date

Where tests have a significant and unpopular position in the workflow of most developers, some of us skip that part because we have no time to do it, and documentation is even less popular. “Documentation is a waste of time”; “Most documentation is already outdated before it is written”;
“Once written, we will never look at it again”. To mention a few excuses not to write any documentation. And this outlines the problem.

As a contractor, I have worked for various companies writing their own code, trying to build a stable product for their customers. Some are more successful than others. One project is bigger and more complex than the other. All of them are beautiful projects, serving the purpose they were built. But they had one major issue in common, they needed to be documented.

I will always read the documentation when jumping into a new project or using a new library, don’t you? Many developers complain about the lack of documentation for open-source projects or poor documentation. So we do need documentation. But only if the code is not ours. And we can agree that after a while of working on this significant project, you will only be able to remember some details of a project. And when you have been working on another project, you might feel like you are new to an old project. Jumping on the rolling train is a reality for people like me. But also for your future teammates.

So we need documentation as we need tests because it’s one of the tools that will help us understand the project. Documentation allows you to communicate about your project with people who cannot read the code (yet). But documentation is also another level of abstraction on the technical specification of your project, being your code in the purest format. Yes, your code is the most detailed format to write documentation. It’s even so detailed that we can execute it with a computer. So we write down every application detail but in the wrong format. A format that is not understandable by unspecialized humans.

Facing the problems I mentioned about time, redundancy of information, and the interest of developers writing documentation, it’s good to take a look at how to improve that part. There have been many projects about writing human-understandable code (Natural language programming). But somehow, NLP fails, natural languages are too ambiguous, and computers have difficulty understanding context. They resulted in languages like COBOL, Apple script, and ClearTalk. Complicated to process for computers and humans because of their limited syntax, needing to be more natural. We all (at least I do) like the programming language we use daily. Switching languages doesn’t feel comfortable as well. In my opinion, the secret is the other way around. Going from a non-natural language to a more natural one. This system will not be perfect, but humans can understand imperfect language, especially when it’s their own native language.

For me, this opens a new opportunity. Building a highly complex system which I like to work on, in my own comfortable programming language, producing documentation. This solution solves several issues because we are automating things we do not need to spend much time writing documents. We can spend our time writing code that we like. The documentation is updated when the code changes so that it will be accurate. And because of this, people will be more tempted to read and use the documentation while working on a system. And as the documentation is generated it does not matter that it’s redundant. I’m fully aware of the complexity of this whole project, and I think it’s impossible to remove the need for handwritten docs entirely. But at least we can try to approach the goal. And start automating the parts we can automate.

So next time you start drawing your new architecture, consider integrating this into your code. Use attributes, annotations, and add other metadata to your code to mark the key components of your code. Use the reflection tools like phpstan, deeptrac, psalm, phpDocumentor, and many more to extract information from your code and validate the structure. Define a set of definitions you use in our code and during meetings with your business and customers. And bring that together in a set of wonderful scripts that will help you to create the most admirable documentation set you have ever had. Meanwhile, I will keep dreaming of a world where documentation goes hand in hand with our code.

I’m @jaapio, the maintainer of phpDocumentor, striving to create the best possible documentation framework for the PHP community. That will help us to make a significant step forward in the world of “Living Documentation”. Thanks to Cyrille Martraire for being an incredible inspiration to find a new path for improving phpDocumentor.

Zombies 3 and PHP

I’m weirdly enamoured with the Disney Plus teen musical movie, “Zombies 3.”

Mostly though, it’s not the movie itself. It’s this interview with the Asian, non-binary, queer actor Terry Hu who portrays a non-binary alien main character in the movie:

In the interview, Terry says:

“Holistic representation or true representation of a group or community requires, in my opinion, two types of stories:

1. Stories that revolve around the specific struggle that comes with being that minority.

2. Stories that just include characters that happen to be from underrepresented communities, but their plot doesn’t revolve around that. We’re here. It’s not a phase, trend, or fad. Let’s just integrate and keep going.”

In other words, we need stories where characters from marginalized groups get to live their lives as fully realized human beings, rather than just as symbols for specific social issues.

What does that have to do with PHP?

Well, it’s no secret that I’m passionate about helping to make speaker lineups at conferences more diverse.

A question I get from new speakers is if people should only be speaking about being underrepresented. What if we don’t want to focus on that? What if we want to talk about a tech passion? Like, say, something to do with PHP?

I see it like Terry sees it. We need both kinds of tech talks. So yes, while talking about our experiences as members of underrepresented groups is important, we definitely also need more of us talking about our tech passions: doing what we do, being who we are, sharing what we love. By nature of having different life experiences and cultural history, we naturally see things with a different perspective which will bring added value to the tech event.

Why it matters:

  • Having a mix of people who don’t all look alike and come from the same background makes it a better conference
  • Having different perspectives brings in fresh ideas that benefit everybody!

Actions Steps:

If you identify as a person from a marginalized or underrepresented identity: Consider talking about your PHP passion at a PHP event.

If you are organizing a PHP event: Keep an eye on having a mix of voices in your speaker lineup. Hint: This becomes easier if you also have a mix of voices on your organizing team.

And if you’re in the WordPress community: come join our #diverse-speaker-support channel on the WordPress Slack chat!

Maintenance Art

While catching up with PHP 8.2 recently, I had a chuckle at how deprecated features get top billing — as they should! Knowing well ahead of time what things that work today will break in future versions of PHP keeps developers on their toes. With the mortality of your code in view, thanks to the anticipated end-of-life of things like dynamic properties, adaptation to new and more elegant, resilient, and “correct” conventions is incentivized. Like annual growth rings in trees, this is how a programming language and community can renew itself.

That’s the gist of Brent Roose’s take on deprecation notices and Nikita Popov’s rationale for phasing out dynamic properties. Roose’s two-minute video on the evolution from PHP 5.6 to 8.2 is a brilliant and simple visual illustration of PHP’s evolution along these lines from 2014 to the present. Using the example of a simple data transfer object, Roose shows how the PHP 5.6 version shrinks enormously to a much simpler and overall more elegant block of code. 

Evolution is hard of course. The need to update legacy code causes many headaches and serious challenges. Our forward-thinking maintainers of backward compatibility in WordPress — Tonya Mork and Juliette Reinders Folmer — are concerned that WordPress developers won’t have enough time to refactor their code, not to mention the special challenges of maintaining a twenty-year-old project. Mork, Reinders Folmer, and Sergey Biryukov have been the largely unsung heroes of this daunting task for a long time.

In their discussion of Dynamic Properties and Magic Methods in PHP 8.2, Mork and Reinders Folmer point out that WordPress’s roots in PHP 3 and 4 keep it in a solidly procedural programming universe while PHP continues to advance as an object-oriented language. Core developers need to figure out a way to maintain the behavior of legacy code in today’s PHP without breaking backward compatibility “and still make the code better and more secure and mitigate the PHP 8.2 dynamic properties deprecation,” as Reinders Folmer puts it. “We are actually making our own lives very difficult with [WordPress core’s] no [backward compatibility] break rule,” she notes in the video — with a smile. 

This made me think of an old but outstanding blog post by Jeff Atwood where he discusses “The Noble Art of Maintenance Programming.” “Maintenance programming is widely viewed as janitorial work,” he wrote. I wonder if he had artist, Mirele Laderman Ukeles in mind, whose “Maintenance Art Manifesto” has always struck me as very relevant to programming and web development. They too are fundamentally tied up in traditionally denigrated maintenance tasks. 

Laderman Ukeles saw that every discipline and field of knowledge has “Two basic systems:”

“Development and Maintenance. The sourball of every revolution: after the revolution, who’s going to pick up the garbage on Monday morning? […] Development systems are partial feedback systems with major room for change. Maintenance systems are direct feedback systems with little room for alteration.”

Laderman Ukeles was a young artist and a new mother in 1969 when she wrote her manifesto and declared maintenance is art. She was frustrated with how cutting-edge works of art and high-status labor are divided from the work that makes them possible: parenting, teaching artistic skills and traditions, or just putting on an art show. She created a memorable exhibit based around herself acting as a museum janitor as people looked at the artworks on display. She spent most of her (ongoing) career as the New York City Department of Sanitation’s artist-in-residence. Her first project in that role was personally thanking all 8,500 sanitation workers “for keeping New York alive” in the 1970s.

That’s how we should treat our code maintainers too. Thank them and support them!

Atwood has a similar “maintenance art” take on programming. He quotes several major figures in software engineering who say the denigration of maintenance work is all wrong. Robert L. Glass felt “Maintenance is a significant intellectual challenge as well as a solution and not a problem,” so it ought to be regarded as an important task for the most skilled people. Joel Spolsky wrote long ago that developers are lazy, and the reason they “always want to throw away the code and start over” is that “it’s harder to read code than to write it.”

In a conversation with Andy Hunt, Dave Thomas argued, “All programming is maintenance programming because you are rarely writing original code. …. You spend most of your time in maintenance mode. So you may as well just bite the bullet and say, “I’m maintaining from day one.” The disciplines that apply to maintenance should apply globally.” Hunt agreed, “It’s only the first 10 minutes that the code’s original when you type it in the first time. That’s it.”

Atwood ultimately leans toward this viewpoint but echoes the common WordPress developer perspective that I hear from my colleagues at StellarWP like Jason Adams and Timothy Jacobs. We may always feel “behind” in a “move fast and break things” world, between the revolution of the new and maintenance of the old. But this is a good and honest place to be — very quickly everything is “old” and needs care. 

The special art of development as maintenance is a balancing act.

Laderman Ukeles is still a working artist, and a few years ago a journalist writing about her shared this story:

“When I told her that the neighborhood where I live in Brooklyn is part of the Sanitation Department’s pilot program for collecting compostable food waste, she beamed: “And do you know what that means? It means the system is backing up into your house, making you responsible. Which is what should happen— because you’re part of the system!”

What a great analogy that is for deprecation notices and maintenance programming in open source. We’re all part of the system. We can all help take out the trash or thank those who do it for us. At the end of the year and release cycles, it’s a good time to express gratitude for our best people, our maintenance artists.

Raising the Bar

I started my professional career with PHP in the year 2000. Professional in the sense that I got paid to program, not so much because of my knowledge of PHP. I wasn’t a newbie, though, or at least I didn’t feel like one. At this point, I had accumulated around ten years of experience programming Pascal, C, x86 assembler, and Perl. So when I showed up for this interview for what I thought was a Perl gig and was asked if I could write PHP as well, the cocky young version of me said, “Sure.”

I jumped in at the deep end, facing PHP code that was written the way some people assume it still is written nowadays: big files of markup mixed with logic and potentially exploitable SQL queries. No vendors – the team wrote every bit of code in plain PHP. At this job, there was no VCS or automated tests, and releases were deployed via FTP. Looking back at these cowboy coding days, I wonder how we got away with this. Everything worked out, but I still can’t believe how poor practices and standards were back then. The bar wasn’t just low; it lay flat on the ground.

Of course, it’s easy to frown upon all this in retrospect, but even considering it was a different time, much of what we were doing was subpar. I certainly didn’t bring much to the table back then. My years of experience weren’t exactly worthless, but being able to write code is just a small part of what is required to deliver software professionally. So even at that job, I learned a lot. With every mistake, every project, and every new job, the picture of what makes a good project became clearer, slowly raising the bar higher and higher.

I appreciate all the lessons people taught me that led to building my own practices that I consider crucial and non-negotiable for creating and maintaining projects. One lesson I learned way too late is that it’s worth sharing your knowledge, even if you think it’s nothing unique or original. In the worst case, you tell people something they already know. But there’s a chance you share something that makes a difference to someone. For this reason, I’m sharing an overview of things I wouldn’t want to miss: They make my work life safe and help me keep my sanity.

Version Control

I doubt there’s anyone out there not using a version control system, but I name it anyway. There is, however, a difference between using a VCS and using a VCS properly. This is a whole topic on its own. To keep it short, I only bring up two things I find most relevant:

  1. Make atomic commits. They’re much easier to work with and to understand why a particular change was made. Resist the urge to put unrelated changes in a commit just because you discovered something along the way. Make them separately, maybe even in the main line, and rebase your changes on top of it. Keep it clean.
  2. Learn how your VCS works and how to operate it. You use it every day, so you should have a firm grasp of how it works. If you feel like it is this impenetrable thing that works in mysterious ways, you’re in a bad position. I’m not suggesting you have to be an expert who knows every little detail and niche functionality. The basic concept, the commands you use every day, and understanding what to do when things go wrong will do. Reading Pro Git was an eye-opener for me, and I regret not having read it sooner.

Uniform Code Style

Some things are better left to the computer. For example, I prefer not to deal with code formatting. What’s even more beautiful than a uniform-looking codebase is the absence of unrelated formatting changes in commits that detract from the original change. Don’t waste time resolving conflicts just because of changes with different code styles. Instead, agree with your team on one style, configure PHP Coding Standards Fixer accordingly, and live happily ever after.

Computer Guidance

Another thing computers are better at is catching stupid or sloppy stuff humans do. Static analysis tools like Psalm and PHPStan have become staples in my setup since I first discovered them. I love how picky and precise I’ve become with typing code just from being slapped in the face by those tools so many times. I don’t even trust my own tests, so I let Infection mess with the code to figure out what I’ve missed.

Deployment First

Unless you’re dealing with the rare case of a project that goes straight from development to the bin, you will need to deploy it at some point. Since you have to do it anyway, why not do it first? I got this idea from Growing Object-Oriented Software Guided by Tests, and it has become the first thing I do when setting up a new project. It is way less stressful to start with a tiny deployment pipeline for a blank project and extend it as I go instead of doing it in one big step later in the project. Although automated deployment via pipeline should be the default, I also plan a way for team members to deploy manually from their workstations.

Batteries Included

When I want to work on a project, I should get it into a usable state in no time, ideally by running a single script or make target. I’m not only talking about a usable state from the technical perspective; it should also contain sensible data, like user accounts and other data, to illustrate the existing use cases. I found it best for this data to be stable and only evolve with new features or when uncovered cases get discovered. This way, everyone knows what to expect when they log into a system.

Living Documentation

Having a system you can immediately work with is nice, but if you have to figure out how it’s supposed to work, it’s no fun. Reading the source code and unit tests is a way to get there, but it’s a little tedious. Also, when talking to your client, you must constantly translate between two worlds. On the other hand, documentation written in tickets, wikis, or other documents is easier to digest and discuss for non-technical people but in my experience, it’s more prone to error. For example, people forget to transfer changes or the implementation diverges from the specs for unknown reasons.

To bring these two worlds together, it’s best to document a system along with the code in the VCS. I like Gherkin for its simple structure that makes sense to both technical and non-technical people. Illustrating features using examples is a great way to ensure everyone is on the same page. In conjunction with Behat, you get executable specifications that prove that the system behaves accordingly. Writing the specification for a new feature first gives the team a definition of done, preventing gold plating.

Rapid Feedback

Since we’re on the topic of testing, I want to emphasize the importance of rapid test feedback. Slow-running tests interrupt your development flow and slow down the deployment process. While unit tests should be blazing fast anyway, I expect a similar pace for acceptance tests. Depending on the project size, this can be a challenge, and it often only works if slow parts can be easily substituted. If an application is coupled too tightly to concrete and slow technology, testing time will increase significantly with each new feature, making the feedback loop slower and slower. Therefore, keeping an eye on that or having a different strategy right from the start is better.

Decouple All The Things

Have you ever used a bit of technology, library, or framework that got you into trouble? Like a library that got abandoned, some nasty BC break, or just the need to replace something with something else? I’ve been there more than once. Instead of introducing proper abstractions, my former self was lazy, thought there would never be a need to replace something, or didn’t want to “over-engineer.” It took weeks of refactoring sessions to pay off that technical debt. The ecosystem has caught up, and nowadays, we have fantastic tools like Rector to automate such activities. This is good if the damage is already done. My learning is to decouple dependencies from the core application by using abstractions and pushing concrete implementations down into a small infrastructure layer. If this is Greek to you, I highly recommend Alistair Cockburn’s paper on Hexagonal Architecture. If you want something more PHP-related, there are Matthias Nobacks’s books Advanced Web Application Architecture and Recipes for Decoupling.

Phel Lang, a native LISP for PHP

For more than two years, Jens and Chema have worked hard to create what Phel is today. Phel is a Functional Programming language that compiles to PHP. It is a dialect of Lisp inspired by Clojure and Janet.

What does Phel look like? 

Here is an example of some Phel code from the website.

# Define a namespace
(ns my\example)

# Define a variable with name "my-name" and value "world"
(def my-name "world")

# Define a function with name "print-name" and one argument "your-name"
(defn print-name [your-name]
  (print "hello" your-name))

# Call the function
(print-name my-name)

Why Phel?

It is the result of many failed attempts to do functional programming in PHP.

We wanted to write a Lisp-inspired (Functional Programming) that runs on cheap hosting providers, and it’s easy to write and debug.

Object-Oriented vs Functional Programming

In OOP you manipulate objects (with methods), and they have a mutable state (as properties).

– Focus on “how you are doing”, mutable data, imperative programming, side effects, looping, …

In FP you have type structures that hold information, and everything else are functions.

– Focus on “how you are doing”, uses immutable data, declarative programming, no-side effects, recursion, …

How different is Phel from others?

Phel is highly inspired by Clojure, which runs on the JVM, and therefore it’s compatible with Java.

So, Phel is a dialect of Lisp that “compiles” to PHP, and therefore it’s 100% interoperable with PHP.


– Built on PHP’s ecosystem

– Good error reporting

– Lists, Vectors, Maps and Sets

– Macros

– Recursive functions

– Powerful but simple Syntax


Want to know more?

Visit the website at and check out the GitHub repository.

A Personal Journey: Launching a SaaS as a WordPress agency founder 

Note: This was originally tweeted as a far-too-long-should-have-been-a-blog-post thread on Twitter. It was the first thread of several (second, third, fourth) threads leading up to the launch of Very minor edits have been made.

There’s almost certainly a fancy German word for the feeling when you are days away from launching your first SaaS product that you thought would take 4-6 months to build and it took almost two years 🤣

This is about to be a heck of a 🧵. Allow me to note a couple of things before we continue:

  1. This is very much a stream of consciousness. I’m going all the way back, remembering everything along the way, and bringing you through my experience.
  2. Yes, I know this should be a blog post. It will be (oh look, now it is 😄). I hope twitter threads keep working for a while (plz and thanks @elonmusk.)

Anyway, the month was December. The year, 2020. The day, the 19th.

A food blogger client reaches out and says “what do you use for a CMS?”

I say, “WordPress, dummy”. Spoiler alert: it was I who was the dummy! They continue to describe what they mean: integrated data sources with blog content, phase management and editorial calendar workflow, team management, and ROI insights.

Holy crap, we’ve been misunderstanding what “content management” actually means to people y’all. at least when there’s a real revenue-generating business behind that content.

I share the very genesis of this whole SaaS idea to illustrate one important point: every good thing starts with listening. Really, really listening. This one client represented a segment of our clients. We started reaching out to them. They all validated the same pain points.

This was a real issue that hadn’t been met in a great way yet. Now, anytime I hear folks mention wanting to start something, but not sure how to assess product market fit, what to build, etc…just like…go to the market and listen.

The exact thing they ask for? Might or might not be what you build – but you’ll learn a ton by listening. So. “Listening” started with our clients that were the market for this.

Then it grew to others in the market who weren’t our clients.

But then “Listening” began to look a little different. I knew enough to know that I knew NOTHING about building a SaaS. I began listening – in a fresh light – to voices I already trusted on topics I hadn’t previously given attention to.

Folks like @chrislema, folks like @asmartbear, and others.

People several orders of magnitude smarter than me. Who were GENIUSES at this thing where I was very dumb. I tried to list my “ability to call @chrislema about stuff” as an asset on my balance sheet, but my CPA didn’t get it.

But still – and this is maybe early January/February of 2021 now – reaching out to folks like him to validate the idea was a massive help as I dove in. But what does diving in look like when you don’t know how to swim?

Well, you guessed it – more listening!

This time, diving deeply into worlds I hadn’t been a part of – startup accelerators, @MicroConf groups, food/DIY/Craft blogger Facebook groups, etc. I spent months trying to do two things:

  1. Recognize how vast the chasm really was between what I thought I didn’t know, and what I really didn’t know.
  2. Use the information gathered, leaning on smarter folks than myself, and try to bridge that chasm.
  3. Profit? (jk)

But legitimately, it was (and spoiler: still is!) so, so, so much listening and learning. This iteration of listening led me to one of my earliest technical (and to date, best) decisions. Using @laravelphp to build the SaaS. I decided to take roughly the entire month of February 2021 to do as deep a dive as one could do into Laravel.

Poring over the documentation, joining all the Slacks, finding and following key leaders, researching canonical resources, and finding small ways to contribute. Being open source and PHP – there’s a lot of lovely overlap – even among community members! – between WordPress and Laravel. Both communities learn (and still have much to learn) from each other.

Comparing the two may be a thought for another day – but for now – love them both. After spending the month building, learning, and prototyping with Laravel – had my initial estimates around a 3-4 month build.

Oh sweet, sweet, dumb, younger Justin. How naive and beautiful you were. So I tried. I really really tried.

We had great clients and our team was crushing it at @zaowebdev – so I could afford to step back for a few months and just deep-dive and try.

For someone who had no idea what they were really doing – I did ok! talked with some geniuses: Had some amazing design work from @shaybocks and some brilliant head-starts from @jasontheadams on the best path forward, and I actually had a really cool, functional prototype that we let some beta testers use in June/July of 2021. BUT THE STORMS WERE A’ BREWING

I knew going into it that it was likely v1 of a few versions of the app. Nothing was precious. But the beta testing group (shout out to you all, you know who you are!) made it clear to me, I had too much technical debt, and scaling issues would come soon.

I needed reinforcements. so this is inside baseball a bit, but you know what the hardest part of running an agency is? Tough clients, deadlines, trimmed budgets, internal team drama, logistics, quality control, communication?

Nah. for me, anyway? Hiring. HARD to do well. In WordPress, which I know well! So now imagine “dummy-knows-very-little-Justin”, hiring killer Laravel developers to bring this vision to life in a scalable, sustainable way.

Again, with the spoiler alerts(!): it took a few tries. More on that to come.

At this point – it’s July 2021. I’m burnt out. Things were getting busier at @zaowebdev – and we were growing and hiring more there as well – and managing that on top of this was starting to kill me. So I took August mostly off from dev work and focused on hiring. Interviewed around 100 Laravel developers. I have…many, many thoughts on the state of DX/developer experience levels in WP vs. Laravel.

But they are very, very different.

But also? kind of the same. In that, you can usually get a sense pretty quickly of who really knows what they’re doing and who’s sort of faking it. By the beginning of September, we had 3-4 folks shortlisted and working together to assess what I had built, review business logic, and make assessments as to whether or not what I built was usable or not.

Good news, it was!

Spoiler alert: it was not

So I head back over to Zao-land – we had separate, significant initiatives going with both @Pagely and @GoDaddy (pre-acquisition, lol) – and it took most of our entire team’s time, mine included.

But hey, the new SaaS team was killing it, using my code, let’s go! So aside from weekly check-ins and the odd review here and there – they were essentially on auto-pilot in September and October 2021.

It was 100% my bad for taking my eye off the ball with them. Looking back, it wasn’t their fault at all, it was mine. I don’t know, in that season, that I could or would have changed anything…but it set us back a few months.

Still needed reinforcements…just better ones. So we’re solidly at “12 months ago” now – 3-4 months past my estimated launch date.

I start re-scouring “Laravel Twitter” and “Laravel Slack” – places I went to learn before, and I now went to hire. Always be learning. Found some lovely, brilliant developers. They were way smarter than me, and way, way smarter than the other folks we had working on it.

They took two looks at my code and said, “Hey nice try! scrapping it all.”

I cried a little. And we scrapped it all. It was a fantastic decision. Lesson learned: sunk cost fallacy is real! But the “fallacy” part of that is key.

Don’t lose yourself on sunk costs. Everything is an experiment. Experiments teach us a lot. “Winning on your first try” isn’t the right definition of success. So we contract these lovely, brilliant Laravel folks (you know who you are, thank you!) between November 2021 and February 2022 to rebuild the entire app.

From the ground up. Burn every assumption to the ground, nothing is sacred. And they did! And it was beautiful to watch. I’m profoundly aware of how ridiculously privileged my life is that I can pay a fantastic team of devs to help me build something amazing – and use that as a 4 month learning opportunity.

It is and was exactly that, a privilege. But as they say, every new beginning comes from some other beginning’s end. Contract was up. Estimates were “1-2 months of work left”. Re-engagement didn’t pan out. So with a solid new foundation in February of 2022, what do I do – try and hire again or try it on my own?

At this point, I was far more confident in my ability to hire good developers than I was to be a good Laravel developer 🤣.

Back to the hiring board!

Between February and the end of March, I reviewed 350+ applications. Let me tell you, Justin-from-February-and-March-of-this-year made an excellent decision. I was a bit slower to hire this time than the previous summer. Less desperate. More focused on the long-game and folks who would be around awhile.

It paid off, y’all. Ended up sifting through 300 applications from @laraveljobs (shoutout!) trialed about 15 developers from there. Found our first full-time hire from this group (he’s amazing and still our lead engineer). then we had ~50 or so applications from various networks – Slacks, Twitters, etc. Our next hire was from @JackEllis‘s brilliant Serverless Laravel Slack.

Maybe a separate thread in due time on our tech stack (AWS, Vapor, SQS, SingleStore before too long, etc.). So we’re now in June 2022 – two incredible developers with us – cranking away on building out the SaaS vision.

At this point, I’m working with them to assess whether or not that “1-2 months of work left” was real or not. so as it turns out, estimating complex software development timelines is hard. like, way hard.

We were (again with the spoilers!) not 1-2 months away. But how far away? By roughly the end of July, we had a pretty good idea that we were 3-4 months out. Mid-August rolls around, we add another developer to the mix, plus our PM is splitting time between our SaaS and Zao. Five of us, an end in sight, let’s go! Something funny happens when a project goes from the abstract “it’ll be done when it’s done!” to “here’s a deadline we’re committing to”.

Again, German friends, you must have a word for this? Time both slows down and speeds up rapidly. Bugs multiply and reproduce. It’s crazy.

A couple of asides that I don’t want to skim over because they were HUGE wins:

  1. In the middle of all of this – Pinterest opens up their API to us. MASSIVE win. 
  2. Our deep integration with Google, Facebook, Instagram, and all the others – all of which have long approval processes? ALL APPROVED!
  3. WordPress plugin? Approved! Now it’s September of 2021.

Notice all I’ve really talked about up to this point is development? As it turns out, building a SaaS is a lot more than that. In this season (~summer/fall 2022) – we had Gantt chart breakdowns with over 15-20 people on them at times.

Video Editors.


Conversion rate optimizers

Designers (shout out to @VinThomas and @shaybocks!)

And that’s just some of the non-engineering folks. I guess what I’m saying is that there are definitely people who can do all of these things on their own – or at least appear to.

I’m not one of those people. I’ve needed, and been so thankful for, a TON of help along the way.

Remember – I have no idea what I’m doing. Lol, speaking of not knowing what I’m doing.

Many, many, many brilliant people – from validating product market fit, to writing excellent copy, to designing intuitive interfaces, to selling and marketing it all brilliantly, to building it scalably…so many others are doing work behind the scenes. Some day, I’ll have the opportunity to highlight each one of them specifically and show you why I think they’re so special and amazing.

But we’re now, let’s say, 6 weeks ago. Solidly into late September. We have a clear vision of what’s left and where to go. At some point at a team meeting, we say “ok – here’s what we’re launching with, this is what we have to nail. Everything else, we cut”. Those conversations are hard and can hurt.

Dut delay is rarely denial (true in engineering, true in parenting…generally true!)

We’re deep (I mean…deeeeeep) in QA, debugging random SQS timeouts, API rate limits, UX issues. Dealing with small team resourcing stuff (vacations here, maternity leave there, holidays, etc.) And still, everyone is crushing it. Eyes are on the prize. We take all of October and focus deeply on any of those core launch items remaining, fixing other bugs along the way.

We fix a lot of big bugs, and find some new littles along the way.

Currently, our Github tracker has 33 open bugs and 33 closed for Launch. I like that. At this point – everything in the app is functional and complete. We’re in bug-squash mode for the next little bit here, but we’re actually gonna launch the thing! At different points and in different demos – beta testers have told us what a game changer this will be for them and how powerful it is.

I really think it will be neat for the market it serves.

With this SaaS (and Zao, and everything): my mission is margin.

 “Hey Justin, dummy, what does my mission is margin even mean?”

I’m glad you asked.

In the context of this app: it’ll give people back hours of their day (and dollars in their banks). Getting time and money back in your life is really powerful. And when we can leverage tools (like businesses) to give people more…life…in their life? That’s what it’s all about, for me anyway. So those are the stories I’m stoked about.

Not the stories about “what a cool app we built!”, Nah. I’m excited for the stories of our clients and people like them, coming to us saying, 

“Man, you gave me 45 minutes back in my day. That’s not a lot….but it is a lot, ya know?” or “Man, I never know what the ROI on my blog posts actually were. Now that I do, I’m making more money!”

That’s what gets me going, y’all. Anyway – that brings you up to speed! I’ve got my Pre-Launch Checklist in hand. All the folks on all the teams are crushing it.

Oh yeah! it’s called @OtherboardApp 🤣

We launched it on November 20th.

More to come 😎

Versioning a PHP API with Composer

This year I’ve had the opportunity to learn and grow in many different ways. Recently I’ve had reason to dive into the benefits and trade-offs that come with a formal versioning convention. As part of this I’ve been spending time returning to the fundamentals of versioning. One thing I always find useful is revisiting fundamentals with new insights, and so thought this would be a fitting topic to contribute to this blog.

I find the topic of versioning can often get lost in the abstract, which can make it tricky to engage with. Personally, after starting my career in the WordPress ecosystem and later transitioning over to Laravel, it’s a topic that I’ve cobbled together an understanding of over years of partial explanations and practical uses. Therefore I’ve aimed to draw out the specifics, and understand how they apply to the more abstract concepts. I find working with package versions in PHP quite seamless thanks to the Composer dependency manager, and I feel that it makes quite a good use case for walking through how to version an API.

Introduction (1.0.0)

In this post we’ll walk through how to version a PHP API by using Composer. It presumes some general familiarity with the PHP language (7.0+), the Composer dependency manager, and Git. This post will cover what versioning an API means, what a PHP API could look like, the basics of contemporary versioning practices, and how to manage this all via Git and Composer.


First, let’s put some preliminary definitions on the table:

APIAn Appliction Programming Interface. You can think of an API as the outward facing blueprint of how to interact with your application. An intuitive example of an API is a HTTP REST API, but an API could also be derived from a library, a CLI, or any other public interface that you want to make commitments for.
VersioningThe process of specifying meaningful milestones in your application. Versioning allows you to differentiate between different iterations of your codebase. A version has a name associated with it, which can be anything: a text label (i.e Mac OS X Snow Leapard, El Capitan, etc), a year, a number, a Semantic Version, etc.
PackagistThe main public registry for Composer packages at When you run composer install it’s likely that your package installs and updates are managed via the Packagist registry.

For the purposes of this post, we’ll look exclusively at the Semantic Versioning (“semver”) convention. You may be familiar with the format of a semver version name; the name is comprised of three numbers separated by dots, such as 2.1.5. The first number represents a major version, the second represents a minor version, and the third represents a patch version. We’ll cover what these mean later.

An example API

To demonstrate how to version a PHP API, we’ll use an example of a library that determines the Northern Hemisphere’s season of a given month. For example, if you give input of the number 12 (representing the month of December), it should return "WINTER". Let’s look at our example library below:


namespace YourUsername\WeatherLibrary;

// src/Weather.php

class SeasonException extends \Exception {}

interface WeatherInterface {
  *    @param  int  $month  An integer in the range of 1, 2, ..., 11, 12
  *    @return  string  A string of "WINTER", "SPRING", "SUMMER", or "AUTUMN"
  *    @throws  SeasonException
  public static function getSeason(int $month): string;

class Weather implements WeatherInterface {
  *    {@inheritDoc}
  public static function getSeason(int $month): string
    if ($month < 1) {
      throw new SeasonException("The given month must be greater than or equal to 1.");

    if (in_array($month, [12, 1, 2])) {
      return "WINTER";
    } else if (in_array($month, [2, 3, 4])) {
      return "SPRING";
    } else if (in_array($month, [5, 6, 7])) {
      return "SUMMER";
    } else {
      return "AUTUMN";

As you can see, our example is not a HTTP REST API. You can imagine that our example is a part of a library that can be added to a codebase to provide data regarding the weather. As mentioned above, an API is not exclusively a HTTP REST API; it can be any kind of public interface that you want to make commitments for. In our case our API includes the library method getSeason.

To help make our example library easier to understand, you can also find my implementation on GitHub. You might find it helpful to use this as a resource to follow along with. It’s not required to actually follow along with creating the package, but you may find it useful to help cement some of the concepts (nothing beats working through the problem firsthand).

Let’s presume that the snippet above meets our initial needs, and we’re ready to release our initial version of the library. The library’s directory structure currently looks like this:

- weather-library/
  - src/
      - Weather.php

Tagging with Git

Before we commit our changes, let’s set up the Composer file. First, you will need to create an account at Next let’s create a composer.json file in our project by running: composer init --type library. The chosen package name should be {your-username}/weather-library, where {your-username} is the username for your Packagist account. It will also prompt you for some other details, which you can choose as you prefer.

Now that you’ve created the Composer file, you should see that it has set up some autoloading configuration. Be sure to return to your Weather.php file, and replace the YourUsername part of the namespace to match your actual username used in the autoload map. For example, mine is namespace ElliotMassen\WeatherLibrary.

In order to tag our package, we’ll need a Git repository. I’ll be providing the Git CLI commands to set up the repository, but you may also follow along using their equivalents in any Git-compatible GUI application (such as PhpStorm, GitHub Desktop, etc). You can create a local Git repository with git init, and then stage and commit the changes with git add . & git commit -m "Initial commit". As our library is ready to be used by others, and we want a permanent reference to this specific version of the library, we’ll tag our first version of 1.0.0. To create the tag, run git tag -m "1.0.0" 1.0.0.

Now that the local repository is created, and the initial version is tagged, we’re now ready to create the remote repository, which is required in order to publish our package to the package registry. In this post we’ll use GitHub for this. You can either create a repository on or use the GitHub CLI (gh repo create). You will need to ensure that the repository is public, as otherwise we will not be able to publish it to Packagist. Once the remote repository is created, you’ll need to specify it as a remote for your local repository via git remote add origin https://your-remote-repository-here.git. Now let’s push the initial commit to the remote with git push --follow-tags -u origin HEAD, this will push the initial commit and tag to the main branch in the GitHub repository.

At this point if you run git log you should see something like the following:

commit 3d983033baefd8a9ef70f46ce033fbffbe5f150b (HEAD -> main, tag: 1.0.0, origin/main)
Author: Elliot Massen <>
Date:   Thu Dec 8 20:03:49 2022 +0000

    Initial commit

Registering a Composer package

Now that we’ve created a public repository, it’s time to submit it to the Packagist registry. This will mean that we (or anyone else) can install the package in an application, and most importantly that we can specify which version of the package to install. In order to submit the package, you’ll need to login to your account at You can then submit your package. If you would like Packagist to automatically detect new tags on your repository, you’ll need to ensure that it has appropriate access to your GitHub account via a GitHub hook. If you would prefer not to do this, you can trigger a manual update on Packagist after you push each tag. After the package is submitted, we’re ready to install it in a project.

Using the example package

The reason that you would publish a package is so that it can be used by another project (and as it’s a public package, it may even be used by other people). Let’s look at how that works by creating a new project that uses the weather library.

In this example project, we’ll only cater for the code to be run via the PHP CLI, and it will simply echo the season of the current month. However with more time (and a longer blog post!) you could create a web application that makes use of the package in other ways.

To set up the example project, create a new directory (outside of the previous one used to create the library), and run composer init && composer install. This project won’t be submitted to Packagist (or be pushed to GitHub) so you can name it however you like. Within your directory, create a new file called index.php and paste the following in:


// index.php

require "vendor/autoload.php";

$currentMonth = date('n');

try {
    echo "The current season is: " . \YourUsername\WeatherLibrary\Weather::getSeason($currentMonth);
} catch (\YourUsername\WeatherLibrary\SeasonException $e) {
  echo "ERROR: " . $e->getMessage();

This code gets the current month, and uses it as an input to our getSeason method. As good practice, we are also catching the documented potential SeasonException.

Let’s run the project with php -f index.php. And… oops, it seems like there’s been an error:

Warning: Uncaught Error: Class "YourUsername\WeatherLibrary\Weather" not found in php shell code:1

Our Weather class isn’t available to our code, because we haven’t yet installed the package. So, let’s resolve that! Run composer require {your-username}/weather-library. This will consult the Packagist registry, and install your package. It will also update the composer.json & composer.lock files to add the dependency, and store the specific version in use. After that’s finished installing, make sure to update both occurances of YourUsername in index.php to match your namespace, and run php -f index.php again. This time it should work, and the output should be:

"The current season is: WINTER" // Or whatever the relevant season is in the month you are reading this post

If you open your composer.json file, you should now see that your package is listed in the require object. The key will be the package name, and the value will be the version constraint. The current constraint is ^1.0 which means that when running composer update the latest version of the package will be installed, so long as it is greater than or equal to 1.0.0 and less than 2.0.0. This will be important in the next sections.

Now that the package has been created, and used in a project, we’ll cover how to handle changes to the package. In the following sections we will investigate all three of the different semver version types: patch, minor, and finally, major.

Patch version (1.0.1)

A patch version is a version in which a backwards compatible change is made that doesn’t introduce new functionality, for example a bug fix. Usually this is when some part of the existing functionality behaves in a way that is different from what was expected.

Let’s return to the example library to make a patch version. Currently the getSeason method accepts a $month parameter of any integer than is greater than 0, otherwise it throws an exception. However this did not account for integers that are greater than 12! Without making a fix, it means that if the function receives the number 100 it will return "AUTUMN" which isn’t correct, and could cause a nasty bug in someone’s codebase. Let’s fix this by introducing another if statement to our getSeason method:

public static function getSeason(int $month): string
  if ($month < 1) {
    throw new SeasonException("The given month must be greater than or equal to 1.");

  if ($month > 12) {
    throw new SeasonException("The given month must be less than or equal to 12.");

  if (in_array($month, [12, 1, 2])) {
    return "WINTER";
  } else if (in_array($month, [2, 3, 4])) {
    return "SPRING";
  } else if (in_array($month, [5, 6, 7])) {
    return "SUMMER";
  } else {
    return "AUTUMN";

With this fix in place, we’re now ready to release our patch version. After committing the change, run git tag -m "1.0.1" 1.0.1 to tag the new version. Push the commit and tag to the remote repository as you did previously, and then we can proceed to update the dependency in the application.

In the example application run composer update to update the library to the 1.0.1 version. As before, this will consult Packagist and fetch the most recent version of the package that meets the version constraint. After it’s updated you should still be able to run your application with php -f index.php. You should also now be able to test the fix with the following change (as before you will need to update occurances of YourUsername):


// index.php

require "vendor/autoload.php";

$currentMonth = 100;

try {
    echo "The current season is: " . \YourUsername\WeatherLibrary\Weather::getSeason($currentMonth);
} catch (\YourUsername\WeatherLibrary\SeasonException $e) {
  echo "ERROR: " . $e->getMessage();

When you run this, you should now see the error message: ERROR: The given month must be less than or equal to 12.. If we had run this with version 1.0.0 of the library then we would have not got the expected error message, and instead would have incorrectly seen "The current season is: AUTUMN".

Notice how in our change to the library method we haven’t changed the method signature; it still remains a static method that takes a single integer parameter, returns a string, and potentially throws a SeasonException. We should expect that any codebase that has installed our library has factored this signature into it’s use of the method (for example, the method call should be wrapped in a try-catch, or have documented that the containing block throws the exception). The consumer should expect that they can keep pulling patch versions for their current version (eg. first 1.0.0, then 1.0.1, then 1.0.2, etc) and retain the exact same API. However this is limiting if we want to move beyond fixes, and add completely new functionality to the API…

Minor version (1.1.0)

A minor version is a version in which a backwards compatible change is made that introduces new functionality, for example a new endpoint, function or method. Usually this is necessary when you are releasing a new feature to your package.

Let’s see what that would look like by updating the Weather class in the weather library.

class Weather implements WeatherInterface {
  public const WINTER = 'WINTER';
  public const SPRING = 'SPRING';
  public const SUMMER = 'SUMMER';
  public const AUTUMN = 'AUTUMN';

  *    {@inheritDoc}
    public static function getSeason(int $month): string
    if ($month < 1) {
      throw new SeasonException("The given month must be greater than or equal to 1.");

    if ($month > 12) {
      throw new SeasonException("The given month must be less than or equal to 12.");

    if (in_array($month, [12, 1, 2])) {
      return self::WINTER;
    } else if (in_array($month, [2, 3, 4])) {
      return self::SPRING;
    } else if (in_array($month, [5, 6, 7])) {
      return self::SUMMER;
    } else {
      return self::AUTUMN;

In this change we have expanded the library’s API by introducing four new public constants, with one constant for each season. As these are new aspects of the API that we’ll be making commitments to, this qualifies these changes under a minor version. Although this does also make some changes within the getSeason method, it has not changed anything about the method’s signature, nor the values that are returned. All that has changed within the method is how we store those values, which (by itself) isn’t of consequence to the consuming application. The thing that makes this a minor version is the fact that the constants we’ve introduced have public visibility, and that we are inviting the consuming application to use them.

Following the process outlined in previous steps: commit that change, tag it as 1.1.0 and push them to the remote repository.

Let’s update the dependency in the example application again via composer update. Now let’s make use of the new constants (please forgive me for these contrived examples… I’m really reaching now).


// index.php

require "vendor/autoload.php";

use \YourUsername\WeatherLibrary\Weather;
use \YourUsername\WeatherLibrary\SeasonException;

$currentMonth = date('n');

try {
    $season = Weather::getSeason($currentMonth);

  if ($season === Weather::WINTER) {
    echo "It's winter";
  } else {  
        echo "The current season is: " . $season;
} catch (SeasonException $e) {
  echo "ERROR: " . $e->getMessage();

When you run this, in a winter month, you should now the new "It's winter", and, if not in a winter month, then the same message as before. If we had run this with version 1.0.0 or 1.0.1 we wouldn’t have had access to the Weather::WINTER constant, and would have received an error. This is why this counts as a minor version, and not a patch, as there are new aspects our API.

Notice how in our change to the library method we (again) haven’t changed the method signature; it still remains a static method that takes a single integer parameter, returns a string, and potentially throws a SeasonException. However we have introduced some new aspects. The consumer should expect that they can keep pulling minor versions for their current version (eg. first 1.1.0, then 1.2.0, then 1.3.0, etc) and that pre-existing aspects of the API will remain the same. However this is limiting if we want to change those pre-existing aspects…

Major version (2.0.0)

A major version is a version in which a backwards incompatible change is made. This change can break the existing API, for example by refactoring or removing functionality.

Let’s see what that would look like in the example library (make sure to update YourUsername in the namespace):


namespace YourUsername\WeatherLibrary;

// src/Weather.php

class SeasonException extends \Exception {}

interface WeatherInterface {
  *    @return  string  A string of "WINTER", "SPRING", "SUMMER", or "AUTUMN"
  public function getSeason(): string;

  *    @return  bool  Whether the month is in winter.
  public function isWinter(): bool;

class Weather implements WeatherInterface {
  public const WINTER = 'WINTER';
  public const SPRING = 'SPRING';
  public const SUMMER = 'SUMMER';
  public const AUTUMN = 'AUTUMN';

  protected int $month;
  protected string $season;

  * @throws  SeasonException
  public function __construct(int $month)
    if ($month < 1) {
      throw new SeasonException("The given month must be greater than or equal to 1.");

    if ($month > 12) {
      throw new SeasonException("The given month must be less than or equal to 12.");

    $this->month = $month;
    $this->season = $this->determineSeason();

    protected function determineSeason(): string
    if (in_array($this->month, [12, 1, 2])) {
      return self::WINTER;
    } else if (in_array($this->month, [2, 3, 4])) {
      return self::SPRING;
    } else if (in_array($this->month, [5, 6, 7])) {
      return self::SUMMER;
    } else {
      return self::AUTUMN;

  *    {@inheritDoc}
  public function getSeason(): string
    return $this->season;

  *    {@inheritDoc}
  public function isWinter(): bool
    return $this->season === self::WINTER;

Although we’ve made a few changes to our library, the incompatible change is the one to the method signature for getSeason:

      @returns  string  A string of "WINTER", "SPRING", "SUMMER", or "AUTUMN"
  public function getSeason(): string;

Previously the method was static, took a single integer parameter, and threw a SeasonException, but now it is no longer static, takes no parameters, and throws no exception. You can easily imagine how this change would break an application if they updated to this version without also changing their integration.

As with the previous versions, let’s commit, tag and push this with the version of 2.0.0. If we now return to the example application and run composer update you will find that the application still works as it previously did. Why is this? The answer lies in the composer.json file. The version constraint that was added at the start (^1.0) means it will install any version above and including 1.0.0 but less than 2.0.0 . The constraint has protected the application from breaking when the new major version, 2.0.0, was released, which shows the strength of understanding and setting these constraints appropriately.

At this point it’s worth pausing to consider the reasons why you might want to update to the latest major version of a package that are beyond pure functionality. If the current version works for you, why not continue using it? This is where the reality of maintaining a package comes in (which you can find a plethora of good resources about elsewhere). If it the package is continuously maintained, perhaps the maintainers have documented some kind of “end-of-life” process for each major and/or minor version, like the PHP language itself, or the Laravel framework. In a worst case scenario, the package might not even be maintained indefinitely; perhaps the maintainers are not paid for their work, or the package has been abandoned. It is important to have an awareness of these realities as it can impact your ability (as a consumer of the dependency) to receive bug fixes and security support, which can be crucial for the usage of an application, and the protection of user data.

Presuming that we are interested in getting the latest API of the weather library, let’s give it a go and see what updates we’ll need to make to support 2.0.0 of our fictional package. Run composer require {your-username}/weather-library:^2.0 to update the version constraint and install 2.0.0 (as mentioned before composer update alone will not be sufficient this time).

Firstly, let’s not make any code changes to our integration with the package. Run the application, and you’ll see the following error:

Fatal error:  Uncaught Error: Non-static method {YourUsername}\WeatherLibrary\Weather::getSeason() cannot be called statically

This gives us an indication of what’s changed about the API. So, let’s fix this:


// index.php

require "vendor/autoload.php";

use \YourUsername\WeatherLibrary\Weather;
use \YourUsername\WeatherLibrary\SeasonException;

$currentMonth = date('n');

try {
  $weather = new Weather($currentMonth);
} catch (SeasonException $e) {
  echo "ERROR: " . $e->getMessage();

if ($weather->isWinter()) {
  echo "It's winter";
} else {  
  echo "The current season is: " . $weather->getSeason();

We’ve now updated our application to support the latest major version of our package. Our application still performs the same functionality, but uses the new API provided by our package to do so. You can also see that the application has become a little nicer to read too, which is an added benefit. For a well supported package you might expect some kind of upgrade guide to help you with this process.


After all that, we’ve worked through releasing a patch, minor, and a major version of a PHP package by making use of Git and Composer. All of this was in accordance with the semver versioning convention, which is used by many contemporary applications and packages.

If you’re interested to get a wider sense of this topic, you can find a similar systems in other languages, such as Javascript and NPM, or Rust and Cargo. There are also other versioning conventions other than Semantic Versioning, such as Calender Versioning.

I hope that this helps to enable you to clearly define and document an API in PHP, so that consumers of your API’s can manage their expectations about the severity of a change to it. At the very least you’ll always be able to know whether a month is in winter or not!!