It’s been one year since I decided to switch from PHP to Kotlin. I want to share with you my personal feelings about this change. Additionally, I would like to recommend Kotlin as a possible choice when looking for a new opportunity.
Is PHP a bad language?
Of course not. The latest PHP releases (8.0, 8.1) introduced a lot of good things, such as attributes, defining properties in constructors, a match expression, a null-safe operator, enums, and the “never” return type. When I was switching to Kotlin, all these things were missing.
The main issues for me with PHP were not the code style and code structures – these things are pretty much similar now:
- “never” return type in PHP -> Nothing in Kotlin
- enums = enums
- promoted constructor properties -> primary constructor properties
- null safe operator = null safe operator
- match expression -> when expression
As you can notice, PHP developers might be inspired by JVM-based languages. What are the potential benefits of switching to Kotlin/JVM?
Concise syntax
Everyone who has had to perform operations on arrays or strings in PHP knows this pain. You need to learn a lot of array_* and str* and str_* functions that are not always self-descriptive. Take a look at some examples below.
<?php
// ARRAYS
$array = [1,2,3,4,5,6];
// we need to use count function to get array length
$arrayLength = count($array);
// we need to combine 2 functions to get reversed array with elements multiplied by 2
$multipliedArrayReversed = array_reverse(array_map(fn(int $value): int => $value * 2, $array));
// take a look at filtering - array_filter has reversed function arguments compared to array_map
$oddsOnly = array_filter($array, fn(int $value): bool => $value & 1);
// STRINGS
$string = "Hello World";
// To get only second word of the sentence we can use 2 ways (there are more, but here are 2 examples)
$onlySecondWord = substr($string, strpos($string, ' ') + 1);
// OR
$onlySecondWord = explode(' ', $string)[1];
// another function to reverse the string
$reversedString = strrev($string);
// to check if the string starts with another string we use str_starts_with
$startsWithHell = str_starts_with($string, "Hell");
// but if we want to check if string contains another string arguments are in reversed order
$containsHell = str_contains("Hell", $string);
Now, let’s compare it with the code in Kotlin and make your judgment on which is more readable.
fun main() {
//ARRAYS
val array = arrayOf(1,2,3,4,5,6)
// array size is just a property not another function
val arrayLength = array.size
// methods are chained and lambda function can be used
val multipliedArrayReversed = array.map { it * 2 }.reversed()
// the order of arguments is consistent
val oddsOnly = array.filter { it % 2 != 0 }
// STRINGS
val string = "Hello World"
// there are many helpful methods included comparing to Java
val onlySecondWord = string.substringAfter(' ')
// just another method to reverse the string
val reversedString = string.reversed()
// no need to remember about the arguments order
val startsWithHell = string.startsWith("Hell")
// again - no need to remember about the order
val containsHell = string.contains("Hell")
}
Kotlin/JVM also supports things unavailable in PHP, like generics and extensions. Both are very useful and can make the code very clean and reusable.
Differences in functional composition
Let’s first take a look at the fold function written in PHP and Kotlin.
<?php
function fold($array, $callback, $initial=null)
{
$acc = $initial;
foreach($array as $a)
$acc = $callback($acc, $a);
return $acc;
}
fun <T, R> Collection<T>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
PHP does not support typed/generic collections. When you iterate over the array, you never know what kind of values are inside. In functional programming, it might be a problem, especially if you would like to write a high-order function working on a specific type.
In PHP, you can’t pass the structure of the callback function. It makes development harder because the IDE will not resolve it. Also, it’s harder to write the function itself because of no generic types support.
The last thing is that PHP has no extension functions support. It forces you to always write a standalone function that cannot be connected to a specific class or interface.
Multi-module projects
JVM environment and tooling have much better support for organizing the code in multi-module projects. In PHP, to avoid using one class in another module, the only thing you can do is to write some architecture test that will check for misuse of namespaces. In Kotlin, you can specify that the class is internal, which makes it visible only in a single module.
From the tools perspective, Gradle and Maven provide out-of-the-box support for multi-module projects. You can enable different plugins and require dependencies per module. I don’t remember this kind of possibility while using PHP Composer.
Performance
The services I’m currently maintaining have a 99 percentile response time of around 6ms (running on Spring Boot 2.6.x). Achieving this kind of performance with PHP and Symfony framework is very hard and in most cases almost impossible. My personal best in PHP is an average of 30ms per request for a similar-sized project.
PHP is an interpreted language. That means the application instantiation needs to be created for every single request. That also means requests cannot be processed asynchronously — there is no way to make the Nginx server suspendable as it needs to wait for the result. PHP now provides JIT and preloading, but the results are incomparable to the JVM stack.
Kotlin offers easy achievable async request handling by using coroutines. Frankly, without any tweaks, it can provide noticeable results and multiply the number of requests that could be handled using the same amount of resources.
For JVM, there is another possibility to boost the performance — GraalVM, a high-performance JIT compiler. It’s something that can be used with relatively low effort and brings huge benefits. Also, Spring is working on official support for GraalVM — Spring Native.
Official libraries availability
PHP is not well officially supported by companies around the world. Usually, when looking for an SDK/library, it is not provided from the official source. That means the quality is also questionable. It happened multiple times that the project was suddenly abandoned without replacement. Sometimes I was forced to fork the repository and add some required fixes on my own. What’s more, PHP numerous times misses the libraries for technologies.
Kotlin can use Java libraries. Java is well supported — almost all companies/projects I know release official SDKs/libraries for JVM. What’s more, many libraries support Kotlin directly. It brings a lot of stability and makes the project way more maintainable without fear of suddenly losing any of the dependencies.
Communication Protocols Support
When working with microservices, the communication aspect is critical. Using a wrong or slow protocol may cause a lot of problems. It is enough to have only one bottleneck to slow down the whole architecture. I prefer to use gRPC as the communication protocol between (micro)services. It provides decent performance. Using the Protobuf models, you can easily define the API contract for each microservice. There are other options like SOAP or Java RMI, but nowadays they are not in common use, which is why I won’t dig into those.
Unfortunately, PHP does not offer good support for gRPC. You can generate a client, but running a gRPC server is hard (or impossible) to achieve. Also, not all Protobuf syntax is supported by the PHP generator. Someone can say we can still use OpenAPI. Unfortunately, it is again a much less performant solution than gRPC with Protobuf and harder to work in the long run. There is a great comparison of those two protocols here.
Message Brokers Support
In PHP with Symfony, to consume messages from a broker like RabbitMQ or Kafka, it is necessary to run a command that is executed separately from the main application flow. On top of that, you need to set up a proper supervisor to keep it running forever. Now multiply it by the number of queues/topics to consume messages from. In JVM, it can be performed easily with multi-threading. The application runs as a process and has access to CPU cores and threads. You can allocate one of the threads (or more) and run the consumers inside the same app instance. Writing event-based communication is much easier to implement and test. You can also think about creating a module that only performs message consumption.
What I miss from PHP is the Symfony Messenger library. I could not find any similar project for Java/Kotlin that would offer this kind of easy configuration for sending events to various message brokers or data storage. It also allows setting retry/dead-letter policies and sending to multiple destinations.
Creating Separated Modules/Libraries
PHP has Composer, and JVM has Maven/Gradle. Creating and managing a library in Composer is not so straightforward. I consider it much easier in the JVM world, especially when using GitHub Actions with GitHub Packages. All you need to do is to use one step in the workflow.
- name: Publish artifact
uses: eskatos/gradle-command-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN_WITH_WRITE_PACKAGES_PERMISSION }}
with:
arguments: publish
The package is automatically attached to the correct repository and can be consumed in other projects inside the same organization. You only need to add a repository to your Maven/Gradle file. Unfortunately, this kind of support is not offered for PHP.
Another important thing is local development. Using the mavenLocal()
repository and publishToLocalMaven
task, you can easily test your changes on the fly. In the PHP world, you need to add a specific repository pointing to a local directory and later run the composer update
action every time you change something.
Observability
In my job, we put a lot of effort into observability. Our JVM/NodeJS/Ruby-based services use the OpenTelemetry standard to collect the data and publish it to HoneyComb. PHP support for that is currently only in the pre-alpha stage and should not be used in the production environment. It is a massive downside in the world of distributed tracing. Having no possibility to properly apply the telemetry, we are limited by the PHP services with how far we can observe. We can still use tools like NewRelic, although if we compare the prices, it is a few times more, and the functionality is not quite the same.
IDE/Debugging Support
Kotlin and IntelliJ are both developed by JetBrains. It is not surprising that the IDE support for Kotlin is almost perfect. Not only does it always support the most recent syntax, but it also provides great integration with JVM tools/frameworks like Gradle, JUnit, and Spring Boot.
The most visible difference is debugging integration. If you want to debug Java code locally or remotely, it just works. For the local environment, you don’t need to set up or start anything additional. For remote, you only need to provide a debugging port. Also, the debugger in Java has much less impact on the performance — the application starts a little slower, but later on, there is almost no difference.
For the PHP XDebug extension, there is a full article with all the steps to be done. As you might have noticed, it is a lot. Some time ago, working with XDebug usually meant that you couldn’t even boot the Symfony framework if the cache was not warmed up without the debugger enabled. I heard it changed a bit with XDebug 3, but I didn’t have a chance to test it. One year ago, PHP debugging was a painful experience.
Multiplatform Code
A great strength of Kotlin is its multiplatform capability. With Kotlin Multiplatform, you can create a library with common code and its platform-specific implementations for JVM, JS, and Native platforms. Once published, a multiplatform library can be used in other cross-platform projects as a dependency. (source: https://kotlinlang.org/docs/multiplatform.html)
In my company, several libraries are used on both mobile platforms. Unfortunately, there is no substitute in the PHP world.
However, Kotlin is not only a backend-oriented language like PHP, which can be a downside when trying to find a proper solution on the internet. You will need to go through all the articles that are usually relevant only to the mobile world.
Frameworks/Libraries Support
It is probably the most surprising point for me, but I have to say that PHP frameworks’ documentation is way better than that of Java/Kotlin. When working with Symfony, almost everything could be resolved by looking into the official documentation and examples. For Kotlin, I sometimes needed to spend several hours to find a proper solution for more complex cases. Maybe it’s because I’m only using Spring Boot and a bit of Ktor. Maybe for other frameworks, it is better?
If you care about the clean separation of domain models and infrastructure, you might also find it hard to define the database mappings in separate XML/YAML files like Doctrine in PHP. Annotations are most likely the only way to go. Similarly, it applies to validation — writing it in a separate file is not easy to achieve.
While working with ORM, I also miss one thing about database migrations. Doctrine provides its own migrations library. It allows autogenerating the migration script from entity mappings. It’s very convenient, especially when you start doing a new project, and it happens often. Hibernate does not support it. You can achieve it by using external libraries, but it’s not easy to do and will most likely take several hours of your life.
Should You Try Kotlin as a PHP Developer?
I mentioned things that I have already experienced and considered the key factors for actually trying to switch from PHP to Kotlin/JVM. My feelings are positive, and I will never decide to switch back to PHP. Working with Kotlin brings me a lot of fun that I lost after almost 15 years of work in PHP.
So what’s my general answer to the question? I think it depends.
You need to know that the learning curve is high. Switching from PHP to Kotlin forces you to start thinking about your application and development differently. Things like concurrency, parallelism, and garbage collection need to be learned and taken care of. You need to discover Gradle/Maven to configure your project. You need to find and get familiar with a new Kotlin/Java framework. You need to be aware that sometimes you also need to touch a bit of Java, especially when using libraries that have no Kotlin version yet.
If the above things are not scaring you, you’re good to go. I highly recommend starting from the JetBrains Academy Kotlin Basics Course. The projects there are very well organized, and by doing them, you can learn the basics of the language. The next step is most likely to try to pick one of the frameworks like Spring Boot and create a small backend project. Later on, it’s only up to you how to proceed. It is best if your company supports you in the transition, but I believe anyone can switch with some passion and sacrifice of private time.
Leave a Reply