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.
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.
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;
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
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 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");
}
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.
Example:
var_dump(unserialize(serialize($obj)));
However, this format is only supported by PHP.
function objectToObject($instance, $className) {
return unserialize(sprintf(
'O:%d:"%s"%s',
strlen($className),
$className,
strstr(strstr(serialize($instance), '"'), ':')
));
}
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) {
}
}
}
$obj=new Invoice();
$obj->dateInvoice=new DateTime();
More type juggling.
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.
$obj->new Invoice();
$obj->nodefinedfield=20; // it is allowed
$obj->invoiceDetails=[new InvoiceDetail(),new InvoiceDetail()]; // as expected
$obj->invoiceDetails=["hello",20]; // not as expected
// 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.
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?
A benchmark of example.
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).
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.