I regret using PHP

Jorge Castro
Cook php
Published in
10 min readSep 25, 2021

I regret using PHP.

Right now, I finished a titanic project (around 3 year in the making) and the project is working as expected. However, it feels so fragile and I think I loss a lot of time in some other tasks rather than the own code (including testing/benchmarking libraries, creating our own libraries).

One of the key part of the project is the access to the database, so for this, I use an “orm”. For PHP, you can use any ORM in PHP to do the job, however, this project is not easy and it requires some tricks.

First, I tried Laravel and the performance of Laravel (because I want to use its ORM) could be resumed in a single word: SLOW. So I rejected it completely. I tested other ORM and they are also really slow, so I created one from scratch with two goals:

a) simple
b) fast.

And here is the project

https://github.com/eftec/pdoone

And it works (more or less).

Why do I need an ORM?

Because it is not possible to create a project using the primitive functionalities of PHP (PDO in this case), example:

Using PDO
(it is prone to mistakes)

$stmt = $pdo->prepare("SELECT * FROM myTable WHERE name = ?");
$stmt->bindParam(1,$_POST['name'],PDO::PARAM_STR);
$stmt->execute();
$result = $stmt->get_result();
$products=[];
while($row = $result->fetch_assoc()) {
$product[]=$row;
}
$stmt->close();

Using my library

$products=$pdoOne
->select("*")
->from("myTable")
->where("name = ?",[$_POST['name']])
->toList();

Using my library (ORM)

ProductRepo // this class was generated with echo $pdoOne()->generateCodeClass(['Product']);
::where("name = ?",[$_POST['name']])
::toList();

Then, I found the main problem of PHP: “dynamic typing”.

dynamic typing

Let’s say we have a function that returns a list of products.

$products=ProductRepo::listall();

However, PHP does not know if we are returning a list of products. We could add type hinting, but (as its name says), it is just a type hinting/validation and nothing more. Type hinting is a cheap solution and it is not foolproof at all.

function listall():array {
// code goes here
}

But, there is another problem. PHP doesn’t have the concept of array-of-a-specific-type.

It is Java:

products=new ArrayList<Product>();

It is c#

products=new List<Product>();

And it is PHP

$products=array(); // or simply [], so we could store an object, a number or whatever value here.

objects versus array

Let’s say we have a model of an invoice.

class InvoiceDetails {
public $idInvoiceDetail;
public $idInvoice;
public $product;
public $quantity;
}
class Invoice {
public $idInvoice;
public $customer;
public $date;
public $invoiceDetails; // a list of invoicedetails
}

What if we want to serialize:

$invoice=new Invoice(); // $invoice is an instance of Invoice
$json=json_encode($invoice); // $json is the class serialized
$invoiceback=json_decode($json); // now $invoice is an instance of stdclass.

We could convert it manually but it takes so much resources to have a correct serialization.

So, the best solution is to use an associative array. Why?

$invoice=['idinvoice'=>1...]; // $invoice is an array
$json=json_encode($invoice); // $json is the array serialized
$invoiceback=json_decode($json,true); // now $invoice is an array.

In PHP, we don’t need to use models at all. We could use a bit of them to get some type hinting but again, it is not a perfect solution, neither it is foolproof or help in a big project. It also kills the performance considerably (mainly if you want to serialize/de-serialize).

Are libraries the solution?

There are some libraries to help solving those problems. However, they bring new problems: performance. Many PHP libraries use a lot of “hack” solutions to solve the problems of PHP such as serialization, for example using reflections and such. However, they are a minefield and some of them don’t care about the performance at all.

But why I regret PHP?

Let’s say our initial problem, a function that returns a list of products.

It is PHP

$products=ProductRepo::listAll();
// show the first product if any
if($products!==null && count($products)>0) {
echo $products[0]->name; // or $products[0]['name'] if array
}

There is not warranty that this code will work because the PHP language does not really know that $products has a list of products.

It is c#

List<Product> products=ProductoRepo.listAll();
if(products!=null && products.Count()>0) {
console.log(products[0].name)
}

It will always works because c# knows that the variable products is a list of products (or null) and there is not other alternative.

And it is the implementation of the function in c#

function listAll() {
var result=new List<Product>();
using(var dbase=new BaseExample()) {
result=dbase.Products.toList();
}
return result;
}

It simple and it works. There is not minefield here.

It uses Entity Framework and it reads from the database, converts the value and returns a list of products. The performance hit is minimum (I tested). Why? First, the system does not need to guess the field (the field exists or it raises an error), it does not need to do type juggling and because C# is a compiled language.

And it is the serialization/de-serialization

string json = JsonSerializer.Serialize(products);
var productsdes = JsonSerializer.Deserialize<List<Product>>(json);

And again simple and it works or it raises an error (there is not middle ground here).

For the records and about the comments

About Swoole

I tested Swoole and it works. However, it brings two problems:

a) It does not work on Windows.

You can’t whine anything against Windows, but Windows is king for corporate and big companies. It means for example the office suite and other programs such vpn that only works in Windows. My Dev machine is and will be Windows because it works almost all programs (there are 3 exceptions, one of them is Swoole). ps: The second program that does not work in Windows is Docker. There is a version of Docker for Windows but it is a “tiny”-emulator, so it is not a true (lightweight) container but a bulky one (so it breaks the raisons d’être of container)

b) Swoole is not magical. It is not a new PHP runtime engine on steroids, so if PHP is slow then Swoole too. i.e. Swoole works fine in one cases but bad in others. Also, I am not fancy to add more complexity to the code.

And for this case, C# allows async tasks natively, so we don’t need to runs a separate service so we could multi-task those slow task in a separate thread (and without creating sockets). Microsoft warns us about when we should use it and when don’t because multitasking is not a magic wand that works for every case.

Why I use PHP 5.x?

No, I don’t. I use PHP 8.0.10. However, I can keep my code syntactically compatible with PHP 5.6 (at least the open source libraries). There are some new syntaxes in PHP 7 and 8 but most of them are syntax sugar, so it is not worth to migrate those public library from 5.6 to 7 or 8. Why?

However, there are a few useful functions but nothing else much. I like the opcache of PHP >7.0

Type hinting

One of the new features of PHP 7 is the type hinting and it is another minefield.

Why?
In PHP, type hinting is also type validation, and it is validated not once but every time the function is called.

https://github.com/EFTEC/php-benchmarks#type-hinting

// what you write
function example(Class $field1): string {
}
// what php really does:
function example($field1) {
if(!get_type($gettype)!='Class') {
throw new Exception("field incorrect");
}
// ...
}

So it affects the performance. It is not a big deal in most of the code but some functions are called not once but thousand of times per request of the user. For example, if we have a function that takes 0.001 seconds but this function is called 100 times, then, in total it takes 0.1 second, so it is not so bad, but this measure is per request, so if we have 100 concurrent request, then we are taking 10 seconds in total. If we split in 4 cores, then each core is taking 2 seconds and it is not good.

Luckily, type hinting adds a mere 0.0001 seconds per 5000 requests, however it’s easy to pile up slowness (example, 1000 concurrent users listing 5000 rows and each row calls 1 function)

I heavily use PHPDOC that it doesn’t impact the performance and it is still more powerful than type hinting. It is not ideal but it is give some balance between performance and hinting.

Python in this aspect is more straightforward, In Python, type hinting is only that, a hint, not a validation, so its up to the code to validate the code.

C#: it does not need any of them because the validation is done at compile it, so it is evaluated once, versus PHP that it is evaluated for each call.

Psalm and PHPStan.

Both are nice libraries but again, they are not for free (they add a new “compile stage”). However, they don’t do magic either, they are unable to detect all if not common problems while it forces us to work (for some tasks) in some very precise task.
But, I skip them because PHPStorm does many of the tasks of both without slowing the development process.

But again, both libraries are not even able to determine the right type of field of a variable (if the variable is enough complex).

About C#. C# has some libraries that do the same job. However, Visual Studio (not code) also do a superb detecting problems right out of the bat, but it also allows refactoring.

In general.

PHP is not a bad language. If you look at Python and Django (or Ruby and Rails), PHP is miles better but it is still trying to play a catching-up game with the big guys (Java and C#) then no.

PHP is playing a game of be a dynamic typed language while it tries to simulate a static typed language. Maybe PHP needs to forks itself and create a new static type language.

PHP and Impostor Syndrome

It is what I have seen

class Customer {
private $id;
private $name;
public function setId(int $id) {
$this->id=$id;
}
public function getId():int {
return $this-$id;
}
}

What is the problem with it? The performance of this piece of code is awful. Java could work with this kind of code because Java optimizes the setter and getters. PHP doesn’t have such feature.

And about Laravel, it is a model:

class Customer extends Models {}
// O_O

So we should guess the fields of customer. We could add some hinting and default values but nothing else much. Also, this model relies in some nasty reflection (__set method). Since Laravel is based in this of code and the model is the core of every functionality of the code, then it is the reason why it is slow.

And Symphony is not better, for example the use of YAML (it has some alternatives)

Article:
actAs: [Timestampable]
tableName: blog_article
columns:
id:
type: integer
primary: true
autoincrement: true
title: string(255)
content: clob

And Wordpress is not any better

$customers=['id'=1,'name'=>'john'];

However, it gives the best performance and it is more straightforward. However, it will be hard to maintenance.

But why C

Of course, it does not show the whole picture, for example, servers that doesn’t divulge the technology used.

Also, for employment in USA

  • python 176k jobs
  • java 142k jobs
  • javascript 119k jobs
  • c# 83k jobs
  • php 15k jobs

But again, it is just another measure that it is not complete. For example, Python is more popular than Java, but a Java developer lasts more in a job (the projects are more longest and well paid).

However, why PHP is the more popular (in public websites) and, at the same time, one of the least popular language (in jobs).

For the note:

Many developers have a mis-conception of performance and usability. Example: “my code is slow, however, if I add some framework or library, then it automatically will run better”. Some libraries, specially frameworks, are not performance-wise. They are more focused in stability, validation and general use (and some of them are mainly syntax sugar over anything else).

In Laravel, it is what we do to assign a field:

$object->field='hello';

However, the framework does:

  • it validates if the field has mutators. If yes, then it applies it.
  • it validates if the field is date
  • it validates if the field is cast callable.
  • it validates if the field is json castable
  • it validates if the field is encrypt castable
  • and finally, it assigns the value to an inner array.

And when it validades if the field has mutators, it calls the method studly

public static function studly($value)
{
$key = $value;
if (isset(static::$studlyCache[$key])) {
return static::$studlyCache[$key];
}
$value = ucwords(str_replace(['-', '_'], ' ', $value)); return static::$studlyCache[$key] = str_replace(' ', '', $value);
}

Every time.

What is the problem

for($i=0;$i<10000;$i++) {
$object[$i]->field='hello'; // bye bye performance
}

--

--