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

It is a comparison OOP between CSharp, JAVA, and PHP and why type hinting is not a solution.

Some developers have complained that I used an old code of PHP, so I added an explanation of why.

Let's say we want to model (class) an invoice.

JAVA

public class Invoice {
    private int idInvoice;
    private Date dateInvoice;
    private List<InvoiceDetail> invoiceDetails;
    // encapsulations..
    public int getIdInvoice() {
        return idInvoice;
    }
    public void setIdInvoice(int idInvoice) {
        this.idInvoice = idInvoice;
    }
    // and other setter and getters.
}
public class InvoiceDetail {
    private int idInvoiceDetail;
    private int idProduct;
    private int price; // or decimal
    // encapsulations..
    public int getIdInvoiceDetail() {
        return idInvoiceDetail;
    }
    public void setIdInvoice(int idInvoiceDetail) {
        this.idInvoiceDetail = idInvoiceDetail;
    }
    // and other setter and getters.       
}

Invoice obj=new Invoice();
  • The validations of fields are done at compile-time.

    • For example:

    obj.setIdInvoice("hello"); // will not compile.
  • The JRE (since 1.4) optimizes the setters and getters and it converts internally as to access to the fields directly (like a public field). So, there is not impact on the performance of it in comparison to use a public field. However, "Java" Android (Dalvik) doesn't optimize it.

  • Both serializations and de-serialization will return the right fields because the languages know each field.

  • The code tends to be verbose and it's not clear to use.

    • For example:
    obj.getInvoiceDetails().get(0).setPrice(200); // we set the price of the first element.
    ```

* JAVA can type casting between classes as

  * 
```java
    object obj=new object();
    obj=(Invoice)obj;

CSharp

C# was called a clone of JAVA and maybe it is a clone, but a good clone that is better than JAVA.

C# allows us to use properties and setter and getters. Both are special constructors of the class, so they are not just methods, they are special methods and they are optimized to work correctly.

public class Invoice {
    public int IdInvoice {set; get;};
    public Date DateInvoice {set; get;};
    public List<InvoiceDetail> InvoiceDetails {set; get;};
}
public class InvoiceDetail {
    public int idInvoiceDetail {set; get;};
    public int IdProduct  {set; get;};
    public int price  {set; get;}; // or decimal     
}

Invoice obj=new Invoice();
  • The validations of fields are done at compile-time.

    • For example,

    obj.IdInvoice="hello"; will not compile .
  • The Net Framework optimizes the setters and getters and the properties. So, there is no impact on the performance to use it.

  • Both serializations and de-serialization will return the right fields because the languages know each field.

  • The use of properties is more intuitive than to use setter and getters

    • For example :
    obj.InvoiceDetails[0].Price=200; // we set the price of the first element.
  • C# Can type casting between classes as

    object obj=new object();
    obj=(Invoice)obj;

PHP

PHP is different than C# and JAVA. Both were built on top and based in classes while PHP adopted it (since PHP +4.0). It means that some functionalities are different.

Also, PHP is dynamic typing and it marks a big difference.

class Invoice {
    private int $idInvoice;
    private DateTime $dateInvoice;
    private array $invoiceDetails;
    // encapsulations..
    public getIdInvoice():int {
        return $this->idInvoice;
    }
    public setIdInvoice(int $idInvoice) {
        $this->idInvoice = $idInvoice;
    }
    // and other setter and getters.
}
class InvoiceDetail {
    private int $idInvoiceDetail;
    private int $idProduct;
    private int $price; // or decimal
    // encapsulations..
    public getIdInvoiceDetail():int {
        return $this->idInvoiceDetail;
    }
    public setIdInvoice(int $idInvoiceDetail) {
        $this.idInvoiceDetail = $idInvoiceDetail;
    }
    // and other setter and getters.       
}

$obj=new Invoice();

PHP is a dynamic typing language and even when the fields are defined by a type but the fields are still dynamic typing.

  • The validations of fields are done at runtime everything the expression is evaluated.

    • For example if setIdInvoice uses type hinting then

    obj.setIdInvoice("hello") ; 

    // is the same than to write

    if(is_number("hello")) {
      obj.setIdInvoice("hello") ; 
    } else {
        trigger_error("value must be string");
    }
  • Does using type hinting affect the performance? Yes. If you want to optimize the performance, then type hinting affects the performance (around 10%). Why? Because the validation is done at runtime.
without with type hinting
0.0006339550018310547s 0.0007991790771484375s
  • The PHP compiler does not optimize setter and getters. So when we use a setter and getter, then we are doing a call to a method, then PHP creates a stack, it evaluates the expression and finally, it returned the result. Those operations are quite fast but not as fast as to access to a public field.

  • PHP doesn't have a definition of "generics" (C# and JAVA define generics as class < type >), so when we serialize to JSON an object and de-serialize it, we return a different object.

    • Example:

    // We rebuild the class with public fields because PHP does not understand or treat specially setter and getters.
    $obj=new Invoice();
    $obj->invoiceDetails=[new InvoiceDetail(),new InvoiceDetail()];
    echo json_encode($obj);
    // {"invoiceDetails":[{},{}]}
  • Example de-serialization (JSON)

    var_dump( json_decode( json_encode($obj)));
    object(stdClass)#6 (1) {
      ["invoiceDetails"]=>
      array(2) {
        [0]=>
        object(stdClass)#4 (0) {
        }
        [1]=>
        object(stdClass)#5 (0) {
        }
      }
    }
  • It does not returns the right class but a stdClass that it is not a big deal unless you are using in a function or field that uses type hinting.

    • PHP keeps the current values and types if we use the methods serialize and unserialize.
  • Example:

    var_dump(unserialize(serialize($obj)));
  • However, this format is only supported by PHP.

    • PHP does not support type casting for objects but we could use the next method (it's hacky but it's fast)
    function objectToObject($instance, $className) {
        return unserialize(sprintf(
            'O:%d:"%s"%s',
            strlen($className),
            $className,
            strstr(strstr(serialize($instance), '"'), ':')
        ));
    }
  • So this example returns
    var_dump( objectToObject(json_decode( json_encode($obj)),'Invoice'));
    object(Invoice)#7 (1) {
      ["idInvoice"]=>
      uninitialized(int)
      ["dateInvoice"]=>
      uninitialized(DateTime)
      ["invoiceDetails"]=>
      array(2) {
        [0]=>
        object(stdClass)#8 (0) {
        }
        [1]=>
        object(stdClass)#9 (0) {
        }
      }
    }
  • But what if we set the field $dateInvoice
    $obj=new Invoice();
    $obj->dateInvoice=new DateTime();

Alt Text More type juggling.

  • If we try to json_encode() and json_decode(), then it will crash!. Why?
    Typed property Invoice::$dateInvoice must be an instance of DateTime, stdClass used
  • It is because we don't convert the field internally, so then our type hinting is haunting us back. We need a more advanced way to convert our object (deep casting). However, we couldn't avoid killing the performance.

    • PHP is a dynamic typing language, and it allows the next code
    $obj->new Invoice();
    $obj->nodefinedfield=20; // it is allowed
    $obj->invoiceDetails=[new InvoiceDetail(),new InvoiceDetail()]; // as expected
    $obj->invoiceDetails=["hello",20]; // not as expected

  • So it is not even possible to define a proper OOP because PHP is not good at it. We could force an OOP but we are or killing the performance, or adding more code, so what is the goal of it but to follow a "cargo cult"?

PHP (alternative)

// Factory Pattern

function InvoiceFactory() {
    return ['idInvoice'=>null,'dateInvoice'=>null,'invoiceDetails'=>[]];
}
function InvoiceDetailFactory() {
    return ['idInvoiceDetail'=>null,'idProduct'=>null,'price'=>null];
}

$obj=InvoiceFactory();
$obj['invoiceDetails']=[InvoiceDetailFactory(),InvoiceDetailFactory()];

It fast but it doesn't hinting unless you use some plugins and IDE to do it.

Alt Text

Why we need speed anyway?

It is because our code usually returns and works with list of objects. For example:

// a web service that returns 1000 invoice.
$invoices=[];
for($i=0;$i<1000;$i++) {
    $invoice=InvoiceFactory();
    $invoice['invoiceDetails']=[InvoiceDetailFactory(),InvoiceDetailFactory()];
    $invoices[]=$invoice;
}
echo json_encode($invoices);

If we need to convert every invoice, then we are killing the performance x 1000 times.

For example, what if we want to add PDO?

$sth = $dbh->prepare("SELECT * from Invoices");
$sth->execute();
$result = $sth->fetchAll();
echo $result; // it is an array

If we want to use an object, then we should do

$sth = $dbh->prepare("SELECT * from Invoices");
$sth->execute();
$result = $sth->fetchObject('Invoice');
echo $result; // it is an array of Invoices

However, it will fail because of the type hinting again

private DateTime $dateInvoice;

We could use a library such as Eloquent but we are killing the performance considerably, also we are adding more code, more configurations, and more dependencies, and for what?

Alt Text A benchmark of example.

Final note

So, do you want to put the burden when we develop the code or to give the burden to the end-user? Also, more code does not mean that it's easy to maintenance, commonly simple is better (unless we write spaghetti code).

Extra note.

Let's say the opposite. We have a customer called Cocacola. Cocacola has an old legacy system that while it was ugly and old it worked well. We are commissioned to build a new system with the same functionality. We finish the system and it works but it is slow, considerably slow (it is not a rare case, for example, SAP, SharePoint, Alfresco, Liferay,etc.) are slow. And the customer is not pleased (in fact, it is pissed 😤). You could say "you could add a new machine" but the system is already using a top and expensive server. It is the time when we start disarming all the overhead of the code. If you are a good developer, then you look at the code of the framework and (surprise), you found that most of the code of the framework while it is stable, it's redundant or slow.

About Jorge Castro

Currently: Entrepreneur and Private Consultant
Civil Engineer in Informatics - USACH Chile.
Master in Business Administration (MBA) CEPADE Spain
Microsoft Certified Professional
Oracle Certified Associate
ScrumMaster Certified
Former developer
Former Project Manager

Related Posts