loading...
Cover image for PHP is bad for Object-Oriented Programming OOP

PHP is bad for Object-Oriented Programming OOP

jorgecc profile image Jorge Castro Updated on ・7 min read

PHP is bad for Object-Oriented Programming (aka OOP)

And I bet it also applicable for Python and JavaScript. In general, Javascript is anti-OOP.

While PHP allows to create classes, fields, methods, inheritance and even structs but the main problem is: PHP is a dynamic-type language (or weak type language).

For example this code works:

$var="hello";
$var=20;
echo $var;

The main problem with OOP is serialization. Let's say we have a customer system, where we list customers, and each customer has a category.

Customers

customerId name categoryId
1 John 1
2 Anna 1
3 Bob 2

Category

categoryId name
1 vip
2 normal
3 premium

It is our model(*) classes (or anemic object):

class Customer {
    var $customerId;
    var $name;
    var $category;  
}

class Category {
    var $categoryId;
    var $name;
}

Where the field category is of the class Category.

However, PHP doesn't know that.

What if we call the next code:

$obj=new Customer();
$obj->category->name="vip"; // Warning: Creating default object from empty value

It will crash. This also happens with other languages. In Java, it is called the billion dollar mistake.

So our class Customer could be written as follow.

class Customer {
    var $customerId;
    var $name;
    /** @var Category */
    var $category;

    public function __construct()
    {
        $this->category=new Category();
    }    
}

So we create automatically a new instance of Category each time we create an object or we could use the code as:

$obj=new Customer();
$obj->category=new Category();
$obj->category->name="vip";

() Laravel calls a **MODEL* class, a mix between a Model (anemic domain object) and a service (repository, Dao or DAL class) class. It is something unique. I don't know another language (or framework) that uses this definition of Model. For our exercise, Model = not the definition used by Laravel. It funny how those guys at Laravel breaks a definition while they try to impose their own rules (PSR).

Also, what if we use a field that does not exist?

$obj=new Customer();
$obj->wrongField="hi"; // this field does not exist and it doesn't crash.

PHP does know that the field doesn't exist, but it doesn't care.

PHP and JSON

Now, the main problem is JSON serialization. JSON is heavily used for PHP for share information (web service, cache, and so on).

Let's serialize and de-serialize our object (using JSON)

$obj=new Customer();
$jsonObj=json_encode($obj);
$stdObj=json_decode($jsonObj);

It is the result:

object(stdClass)#3 (3) {
  ["customerId"]=>
  int(1)
  ["name"]=>
  string(4) "John"
  ["category"]=>
  object(stdClass)#4 (2) {
    ["categoryId"]=>
    int(2)
    ["name"]=>
    string(3) "vip"
  }
}

json_decode returns a stdClass Object. It could be fine (because the data is in) but:

  • it skips the constructor, and this behavior is not obvious, i.e., prone of bugs that are hard to find.
  • We are unable to use any logic.
class Customer {
    /** @var DateTime */
    var $date;
    public function __construct()
    {
        $this->date=new DateTime();
    }    
}

$obj=new Customer();
$obj->date->setDate(2019,1,1);  // works
$jsonObj=json_encode($obj); // object (Customer)->json
$stdObj=json_decode($jsonObj); // json->object (stdClass)
$obj->date->setDate(2019,1,1); // Fatal error: Uncaught Error: Call to undefined method stdClass::setDate() in ...
  • it fails if we use type hinting, i.e. to call someFunction(Customer $customer) {...}
$obj=new Customer();
$jsonObj=json_encode($obj); // object (Customer)->json
$stdObj=json_decode($jsonObj); // json->object (stdClass)
someFunction($stdObj); // Fatal error: Uncaught TypeError: Argument 1 passed to someFunction() must be an instance of Customer, instance of stdClass given

function someFunction(Customer $customer) {
} 

Converting stdClass to named Class

However, it is possible to convert a stdClass object into a named class.

But it has a cost.

Let's say the next functions (they don't consider the field Category). I picked those because they are fast, other libraries are painfully slow.

function easyConvert($jsonTxt) {
   return str_replace(['O:8:"stdClass":3','O:8:"stdClass":2']
        ,['O:8:"Customer":3','O:8:"Category":2'],serialize(json_decode($jsonTxt)));    
}

function loopConvert($jsonTxt) {
    $data=json_decode($jsonTxt,true);
    $obj=new Customer();
    foreach ($data AS $key => $value) $obj->{$key} = $value;
    return $obj;
}

These are some functions (the first one is hacky) to converts a JSON string -> proper class.

benchmark time!

conversion time (sec)
arrays 0.0028018951416016
easyConvert 0.031358957290649
loopConvert 0.027074098587036

The first one calls the next method:

for($i=0;$i<10000;$i++) {
    $jsonArr=json_encode($objArr);
    $return=json_decode($jsonArr);
}

And the second and third uses the functions to returns a proper class.

In general, using classes vs. array is around x5 to x10 slower, and we are not considering that the field category is of the class Category, so the performance could be worst. It is not micro-optimization; it is a serious deal.

Some people claim: OPTIMIZE LATER, and it is valid for micro-optimizations but not for the model layer (that is the heart of the project).

What if our customer says "the system is fine, but it's slow, can you optimize it?". It could mean to rebuild a big part of our project. The model class: gone!. Every method that uses it: we need to rebuild it now with arrays. Every view layer that uses it: we need to rebuild it too. So we will end rebuilding the whole project.

Database and bad practice.

Let's say the next pseudo-code

$customers=getAllCustomers(); // it reads from the database the table customers
showTable($customers); // it shows a html table with the customers.

This code could work with little information, i.e. the information that we have on "dev ambiance".

But what if our system has 10 million customers.

  • The system will crash
  • The system will get a degraded performance.
  • The database will "collapse."
  • And other systems that use the database could also get a performance hit.

For our case, OOP could work fine with a small set of data, but it is not scalable. It's not rare to find a system that runs blazing fast, but it turns unusable at the end of the first year.

Now, let's say we use PDO to connects to the database.

function getAllCustomersArray() {
    $sth = $db->prepare("SELECT * FROM customers");
    $sth->execute();
    $result = $sth->fetchAll(PDO::FETCH_ASSOC);
}

function getAllCustomers() {
    $sth = $db->prepare("SELECT * FROM customers");
    $sth->execute();
    $result = $sth->fetchAll(PDO::FETCH_CLASS, "Customer");
}
function getCategory($id) {
    $sth = $db->prepare("SELECT * FROM category where idcategory=$id"); // use prepared statement!!
    $sth->execute();
    $result = $sth->fetchAll(PDO::FETCH_CLASS, "Category");
}
  • getAllCustomersArray() will return a flat array of declarative array.
  • getAllCustomers() will return a flat array of objects. However, it will not set correctly the field category then we should set it manually (for every row).

So, even when PDO is OOP, although the results are not suitable for every situation.

Some systems are simple bad designed. For example:

$customers=getAllCustomers(); // select * from customer
foreach($customers as $customer) {
    $customer->category=getCategory($customer->category->idCategory); // select * from category where idcategory=?
}
showTable($customers); 

In this example, we are doing a query to the table customer and "n" queries to the table "category". What if the table customer has 10000 rows? We will run 10001 queries!. And it is what some libraries do under the hood.

Now, it is a database-friendly function:

function getAllCustomersArray() {
    $sth = $db->prepare("SELECT customerId,customers.Name,categoryId,categories.Name NameCat FROM customers inner join categories");
    $sth->execute();
    $result = $sth->fetchAll(PDO::FETCH_ASSOC);
    foreach($result as &$row) {
        $row['category']=['categoryId'=>$row['categoryId'],'name'=>$row['NameCat']];
        unset($row['NameCat']);
        unset($row['categoryId']);
    }
}

We could do the same with classes too

function getAllCustomers() {
    $sth = $db->prepare("SELECT customerId,customers.Name,categoryId,categories.Name NameCat FROM customers inner join categories");
    $sth->execute();
    $result = $sth->fetchAll(PDO::FETCH_CLASS, "Customer");
    foreach($result as $row) {
        $row['category']=new Category($row->categoryId',$row['NameCat]);
        unset($row->NameCat);
        unset($row->categoryId);
    }
}

But it's a bit slower but it is not x5 or x10 slower, so it is acceptable.

Note: PDO::FETCH_CLASS uses the constructor (if any) after we set the values. It is not obvious. So we could/should use instead PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE. Working with models under PHP is tricky because we are forcing something that it's not, ahem, natural for the system.

But a synthetic benchmark doesn't reflex the reality.

Not really.

For example, what if we need to read a web service JSON Rest and it consists of 1000 rows. We need to de-serialize each row. And what if we also have 100 concurrent customers, so we to do the same 1000 x 100 times. With arrays, we could obtain the same performance 10 times faster (or to serve x10 more concurrent calls). And it is only if the class is simple. If the class is complex then the requirements could escalate. And we aren't used setter and getter. Setter and getter will overkill the performance.

Solution

If we want to use or consume JSON (especially if we need to read many JSON objects) then it's more suitable to use array.

Also, we could avoid generating dozen of Value Objects/DTO

We could even simulate constructors using functions.

function createCustomer($customerId,$name,$category) {
    return  ['customerId'=>$customerId,'name'=>$name,'category'=>$category];
}
function createCategory($categoryId,$name) {
    return  ['categoryId'=>$categoryId,'name'=>$name];
}

$newCustomer=createCustomer('1','John',createCategory('1','vip'));

Another solution

The second solution is to use the method serialize() unserialize() of PHP. They convert correctly an object. However, it is not suitable for a Web Service Rest

echo serialize($obj);

yields:

O:8:"Customer":3:{s:10:"customerId";i:1;s:4:"name";s:4:"John";s:8:"category";O:8:"Category":2:{s:10:"categoryId";i:2;s:4:"name";s:3:"vip";}}

However.

We couldn't work with setter and getter (unless we want to kill the performance).

Conclusion

It's easy to debug and develop the code using classes, especially if the IDE has some type-hinting feature such as PHPStorm:

But it is not for every situation.

For example, OOP works with service classes, controller class, and any class that doesn't involve serialization or a long set of objects.

Also, if we use PHPStorm, we could simulate type-hinting using https://plugins.jetbrains.com/plugin/9927-deep-assoc-completion (it's a free plugin). It's not the same than to use objects; nevertheless, it's close.

Note: The image of the title is from http://www.phpthewrongway.com/

Conclusion 2

But then, is it PHP a bad language?.

In fact no. It's hard to develop but it's more flexible than Java or C# (strong-typed languages), for instance, we don't need DTO/Value Object and we don't need to create a new class if we need to pass a complex argument of a function.

Update

https://dev.to/jorgecc/php-is-bad-for-object-oriented-programming-oop-2-4jf3

Posted on by:

jorgecc profile

Jorge Castro

@jorgecc

You are free to believe in whatever you want to, me too. So, stop preaching your religion, politics, or belief. Do you have facts? Then I will listen. Do you have a personal belief? Sorry but no.

Discussion

markdown guide
 

7.0 has been out long enough that if an article fails to mention strict typing, scalar type hinting and return types, I feel the author didn't put enough work into the research.

 

Not to mention declaring variables with the var keyword. Looks like code written ten years ago.

 

Then how could you set a property?.

Do you want to define as private and to add setter and getter? Then good luck by breaking the performance of the code because of a code that adds nothing but bloat.

 

First, PHP support type hinting but it is a mediocre implementation.

PHP does not support to indicates if a field is defined by a class.

But you already know that.

 

Hi Jorge, thanks for posting! Whew. There's a lot going in this piece. I wasn't unfortunately able to link almost any of things raised in this article to the title. I'll go through a couple:

  • The main problem with OOP is not serialization. OOP is about putting data and behaviour that operates on that data to the same object. It's about encapsulation, inheritance and polymorphism. Good OOP can be written in Python, Javascript and PHP. PHP however uses a lot global functions and primitive data types, but you still can navigate through the issues it has with relative ease.

  • What Laravel calls a model is in fact a very standard name in almost all frameworks that advertise themselves as MVC architecture frameworks. The pattern used here is called Active Record. See for example Django, FuelPHP, PropelORM, CakePHP, Ruby on Rails etc.

 

Your examples are straight from the 90s Jorge.
PHP has evolved a lot since then I think you need to freshen up your knowledge when it comes to PHP as a language.

  • First of all using var to declare properties on Classes is a no no. Also I'd like to point out that you are breaking encapsulation principle of OOP in your examples. Accessor modifiers exist with a reason (public, protected, private) you should've used them instead of var.

  • If you need custom JSON serialisation logic just implement the JsonSerializable interface, I mean it is available since PHP 5.4 and the fact that you are not aware of it just shows that you haven't worked that much with PHP

  • You are instantiating dependency objects in the constructor instead of type hinting and passing them through the constructor your example should've looked like this:

class Customer {

    public $category;

    public function __construct(Category $category) 
    {
          $this->category = $category;
    }
}

This renders your point moot, you wouldn't event be able to instantiate the object without Category, let alone do what your example shows.

You are mixing apples and oranges, a strong type system has nothing to do with OOP, perfect example is the Ruby language, it is an Object Oriented language with dynamic type system.

Most of the languages support multi-paradigm programming nowadays anyways. I'm just saddened by this article because it's full of wrong info that could mislead junior developers.

 

First of all using var to declare properties on Classes is a no no. Also I'd like to point out that you are breaking encapsulation principle of OOP in your examples. Accessor modifiers exist with a reason (public, protected, private) you should've used them instead of var.

Because it's way worse.

First, setter and getters are optimized by the JavaVM (and .net runtime engine) but not by PHP, so in PHP setter and getters are simple methods, i.e. it adds overhead. But let's say we use it.

How we could read a JSON rest (that supplies 1000 rows of data) and store this information into an OOP without killing the performance of the system? If we use setter and getter, then we should read each and every single row creates a new object, parse it and for what, for more code and slowness?. Why we should do that?. OOP?.

Something like that:


$js=json_decode(file_get_contents("\\somejson.json")); // it returns 1000 rows
$objects=[];
$fields=["field1","field2"...]; // 10 fields
foreach($js as $item) { // 1000 times
  $obj=new SomeClass();
  for($fields as $field) {  // 10 times
    $obj->set{$fieldName}($item[$fieldName]); // we will run this code 1000 x 10 times, 10k times.
  }
  $objects[]=$obj;
}

 

Try using PHP 7 if you feel concerned with performance.

PHP 5.x has been discontinued months ago.

Also, opcache is a build-in function.

 

Honestly I don't understand all those "religion" wars around different languages. Every language has it's pros and cons. One can say apples are not oranges enough. Doesn't mean oranges are better, those are just different :D


1) Serialization and OOP are entirely different topics imo.
2) Framework specific implementation doesn't mean PHP's OOP is ugly.
3) Very old examples. And yes, php supports typed class properties.

 
Sloan, the sloth mascot Comment marked as low quality/non-constructive by the community View code of conduct

The author proved to dislike the language and has no clue about it. Please ignore this article. It's bad from start to end and it shows examples that are like at least 5 years outdated.

It's just cool to hate PHP.