5 features of PHP that seem hacks, but save your life
Join the DZone community and get the full member experience.
Join For FreeBack at the PHP Barcelona conference, Ilia Alshanetsky made a talk about some hidden features of PHP. Ilia is one of the people that get his hands dirty in the PHP core, and PHP has indeed many overlooked features.
Of course, the GOTO introduction in PHP 5.3 really sucks and is a symptom of how PHP lacks a shared vision and consistency as a programming language; vision and conceptual integrity that we find for example in Python. Note that I'm a PHP guy: imagine what a Java programmer would say.
However, thinking about this load of features inspired me to write this article: I'll include here 5 features that at first seem an hack, but can save your life while coding in PHP. In fact, you probably use or will use them every day without noticing.
With great power comes great responsibility. -- Uncle Ben
Access to private properties
The Reflection API in PHP 5.3 has the capability of access private properties. Private properties are designed for encapsulation and shouldn't be normally referred to from outside a class, but using reflection to set them is a standard way of dealing with object persistence.
Doctrine 2 follows an Hibernate-like structure, and use this feature to introspect your object graph and put it into a database where each object loosely correspond to a row and each column contain a property value. On the converse, it can also reconstitute an object by setting its private properties. Without the reflection support, object would need to provide an Api for accessing their internal state, an Api open to misuse.
eval()
eval() executes a string containing PHP code. It can easily get out of hand, but dynamic creation of classes can be very handy. For example, eval() can be used for automatic generation of Test Doubles in test suites.
Since Mocks and Stubs must be a subclass/implementation of a chosen class or interface, their code can't be written in advance: they have to extend/implement a particular target class, and override the methods when present to provide hooks for insertion of canned results and expectation on parameters.
PHPUnit generates the code of a Mock and passes it to eval(), so that the new class becomes available for instantiation. PHPUnit must create this class with a generated name, before instatiating the object to pass back.
By the way, Doctrine 2 does the same for proxies without calling eval(): since it reuses the generated code, it saves the class in a .php file that then includes. The source code can be recycled in subsequent HTTP requests, and the effect and the mechanics, apart from the need to pass from a .php file, are the same of eval().
__DIR__
Indeed PHP code should not depend on where it is stored: a class should work even if I include it from another totally different script, which reside in another working directory. The __DIR__ constant provides information a particular file, that do not change when it is included from another point of the application. Thus it can be breaking this principle.
However, during bootstrap, it's very handy to have __FILE__ and, in PHP 5.3, __DIR__ available. They respectively point to the path of the file, or directory containing the file, that they are written in.
Bootstrap files commonly read __DIR__ to setup autoloaders with path referring to fixed __DIR__ instead of the current working directory, which may change depending on the including source. For example:
new MyAutoLoader($libraryPath = realpath(__DIR__ . '/../library'));
The other entry points of the application can then just include the boostrap file, without worrying about their relative position or anything else.
<?php does not need ?>
In PHP, you do not have to close your <?php tags with a corresponding ?> at the end of .php files.
Eliminating this closing tag means that no trailing spaces will be printed: coding standards for major projects include the prescription to never close ?> in class definition files.
Trailing spaces and any other content, when output buffering is not activated, define the end of the HTTP response headers section, so after any output you can't do HTTP redirects or send cookies anymore. Of course you can activate output buffering with ob_start(), that means the output will be kept in a buffer instead of being sent, until you decide otherwise.
The problem is that output buffering is activated by frameworks usually after startup, and due to autoloading your and their class files can be loaded well before then. So, just avoid introducing invisible content after ?> solves the root cause of the problem.
When output is already started and you try to start sessions (which sends a cookie) or do a redirect, PHP will raise a nice error containing the file and line number when output is started. It probably relates to a ?> tag.
__sleep()
I heard Stefan Priebsch saying the __sleep() and __wakeup() magic methods are a crime, and I agree that in the majority of use cases the serialization and deserialization of an object should not include these hooks.
Indeed, __wakeup() is almost nonsensical, since an unserialized object should pull, inside that method, every resource it needs to return in a functional state. Since unserialization means the object has usually lost contact with application resources like database connections, it will have to call some static method or singleton, and rely on the global state to work. It's much better to think of a solution where the object is injected its dependencies, previously detached on storage, upon returning in the land of the living.
In my opinion, __sleep() has instead some usefulness. When developing Doctrine 2 lazy loading support, we created Virtual Proxy implementation that subclass an Entity, and have an internal reference to the Entity Manager. This means that if you try to serialize your loaded User, which has a reference to a Group instance (these are two made-up domain classes), it will pull into the serialized graph the GroupProxy subclass and everything it is attached to, like the Entity Manager (main Facade of Doctrine), and its internal database connection, along with the whole Doctrine 2 runtime.
Our __sleep(), inserted automatically during Proxy generation, excludes the Entity Manager from the picture. If you want to then save your Proxies in the session, an act that triggers serializaton, you can easily treat them as normal objects: after they have been loaded they become equivalent to the real object they represent.
Opinions expressed by DZone contributors are their own.
Comments