I read about PHP's traits on the RFC system a few years back. Knowing Traits and Closures were coming soon was one of the main reasons I decided not to bail for the ruby/python world.
To me traits are actually a better solution than mixins via dynamism, since you get syntax checking via the compiler along with the benefit of compiled speed. You also have less confusion due to mixin conflicts.
It makes my skin crawl to see the first example use case be combining inheriting from singleton and the array class. It looks like PHP is finally getting advanced OOP features and the first thing someone wants to do with them is make a singleton (which violate single responsibility, also introducing global variables and tight coupling) that extends arrays (when delegating to an array instance is usually a better choice).
I know this is a single example, but I feel like I've seen bad OOP practices in PHP so many times in the past. This is such a rich feature but it brings with it a lot of potential for misuse.
What's wrong with singletons in general and why would an object could not assume single responsibility (consider an UIApplication singleton on iOS)? I thought, contrary to what you stated, they solve the issue of global variables rather than introducing new ones.
That class extends SPL ArrayObject class which provides array-like access syntax (operators). It does not mean it has to act like an in-memory array, it could read data from disk, session, database or whatever. Delegating this kind of functionality to a single array would simply not work without further abstraction.
Singletons don't solve problem of global variables, they are global themselves.
They are global shared state than can be unexpectedly accessed from any place in the program.
Encapsulation provided by singletons makes them even more problematic than global variables. You can temporarily overwrite global variable, run function that uses it, and restore global value. Singletons can be designed to prevent that, which kills testability of code that uses singletons (e.g. when you need to replace global DB connection with mock object).
Singletons are global variables++. Read up on the capability security model and ambient authority (Mark Miller's erights.org, Gilad Bracha's Newspeak, Mark Miller's JS work eliminating accessibility of document etc. singletons for safely eval'ing JSON etc.) to understand the security, composability, testability and modularity issues with these idioms.
I don't know the particulars of how PHP's ArrayObject works, but take Ruby for example: instead of inheriting from Array, to make a class that operates like an array, one mixes in the Enumerable module (similar to a trait) and implements an "each" method to handle the low level access. All of the other methods come along with the module. In this way, the lineage of a class isn't important; rather, it's what it actually does that matters. (How meritocratic! :-)
But particulars of any language aside, I think this is an important step for PHP. Once a language gives the ability to compose the behavior of a class through traits, modules, mixins, etc rather than standard inheritance, it becomes easier to separate the data that a class encapsulates from the behaviors that it provides—and that, I believe, leads to more modular code.
Do you know how long it took to finally get rid of PHP 4? Do you know how long web hosters (and with that most open source projects targeting a wide audience) were sticking with PHP4? Even now that there won't be any more php4 releases, there are STILL projects targeting PHP4. There are STILL webhosts only providing support for PHP4. More than 5 years after PHP5 came out
All this even though most PHP4 scripts run unaltered in php5.
And now you suggest to drop backwards compatibiliy to get rid of warts? How long do you think it would take before that new language will get any significant deployment? What good will all the new features be if you are not able to use them in the next 10+ years?
Personally, I like to see them add features in a backwards compatible way. Knowing PHP and programming in general, I can stay away of the bad parts but still use all the new features in the foreseeable (2-3 years) future.
Just have a look at python3 of you want a taste if what you were just suggesting.
> Even now that there won't be any more php4 releases, there are STILL projects targeting PHP4. There are STILL webhosts only providing support for PHP4. More than 5 years after PHP5 came out
The fact that PHP broke many websites when going from PHP3 to PHP4, then at each PHP4 minor release is probably largely responsible for this conservatism.
I agree with you that incompatible changes by removing bad parts is definitely painful, but it makes PHP more sustainable in long term.
This can happen in a more feasible way, e.g. freeze PHP5 (unlike the current situation), don't add any new stuffs; branch out a subset of PHP (removing bad parts), and continue the development there.
Not sure of what kind of legacy features you would want to be removed but it's not like they are just building on top without thinking.
PHP 5.4 will also drop:
- safe mode
- register_globals
- allow_call_time_pass_reference/y2k_compliance and other legacy ini stuff
- "continue 123" syntax (can be replaced with goto). As far as I recall this is done since it's barely used and if removed would allow to implement some opcode performance optimizations.
Dropping some PHP 4 era syntax is obviously out of question due to already mentioned webhosting and backwards compatibility issues.
Depreciating things is moving in the right direction, throwing a depreciated warning in the code. Slow but good way to change developer behaviors towards better PHP code. http://php.net/manual/en/migration53.deprecated.php
A few of PHP's most (publicly) hated components are being removed in 5.4 (which may become 6, from what I can tell this article is lying when it says 5.4 is "around the corner") - register_globals, magic_quotes is being discussed. Even the old asp_tags setting is going. However these have no relevance to me as I haven't used them in years.
If they are going that far, it would probably be better to get rid of more in one go, rather than giving people legacy code headaches with each release.
I guess I would focus on cleanup, method names and parameter orders. I don't know enough about the internals to know if also allowing say $var = $val->substring(0, 2); could be an alias to how values are currently passed into functions.
At least they didn't implement them by trying to come up with every possible mixin you'd ever want to write and then define them in the global namespace. Progress?
Honestly, I would prefer PHP remove all the bad parts of PHP before adding new things.
This has already been done, and the result is called "Python" (or Ruby, or ...). SCNR.
Edit: ok, I realize this was maybe a bit too snarky. But I mean it: if you remove all the somewhat strange parts from PHP, you are breaking backwards compatibility. And at that point, your users have no reason not to move to another, maybe cleaner language (e.g. Python) all together.
So if you want to get a cleaner PHP while breaking backwards compatibility, you might as well just use a different language.
If it's just compiler-assisted copy and paste, users should have been able to do it themselves rather than wait for the maintainers to add one instance of compile-time code generation (out of many more which still aren't present) that requires a platform reinstall to use. This is why languages should be extensible via something like macros.
So... do they figured it out how to fix the PHP's white screen of death? This kind of thing is so annoying that I can't even think of using PHP in my future projects.
I'd assume you'd mean segfaults. It's somewhat painful to track down a segfault in PHP, but if you can compile PHP with debug symbols and then invoke a failing scripts under gdb, it's fairly straightforward.
Syntax errors, calls to @nonexistant_name(), and many others will simply return an empty page and log the error to the error log (error is not catchable, so you cannot even return anything sane to the user)
The fun starts when you hit one of the situations where the whole php stacktrace is `in file "" at 0` (or something similar). I've got 4 php programmers around me and I hear about the "white page" almost once a week, so it's not that uncommon :(
That's not a problem with PHP, you just have your environment setup to return nothing on fatal error.
Shutdown functions still run after a fatal error (something I just learned), so you can display the error or redirect to another page:
function shutdown() {
if (($error = error_get_last())) {
ob_clean();
// report the event, send email etc.
// Redirect
header("Location: http://hostname/error");
}
register_shutdown_function('shutdown');
As for the empty stracktrace, I used to encounter those frequently but I don't anymore. Either Zend has been fixing them up, or I'm doing less work inside of various handlers.
There are several ways of catching uncaught errors and returning reasonable output to the client. First look into set_error_handler and set_exception_handler. Also you can register a shutdown function or set an output buffer callback that looks at error_get_last().
Well, you could turn display_errors to 'on' in the php.ini file, but this is generally regarded as a security issue, as the error message will reveal sensitive info.
There are some situations which cause PHP to just die and not log anything, but those are pretty rare.
It's pretty common practice to have display_errors to on for development, and off for production. Are you not doing that already? Or is everyone developing on production systems?
Bluesnowmonkey already gave you some specific solutions but I wanted to point out that error suppression is generally a really bad idea. If your code encounters an error you should want to know about it.
This is one of the reasons I really like working in Rails. An exception blows the page up properly, and kicks into my exception notifier (sends me an email with the environment and stacktrace) and can display an error to the user. PHP's "soft on errors" approach is just a timebomb waiting to go off. "Fail hard, fail fast" is less forgiving, but ultimately, helps me sleep a lot better at night.
I know that it blows up on fatal errors, and I know that you can have it report or not report errors (error_reporting), but how would I, say, make it terminate with a backtrace if I attempt to fopen a file that I don't have permissions to open? The proper behavior in PHP is to check your file descriptors, but I'd much rather it just give up and go home, since behavior after a failure like that is going to be unpredictable in many cases.
I assume you are talking about fopen-type of functions that are nothing more but wrappers around C standard library functionality.
Use SplFileObject (part of SPL that is part of PHP core) for object-oriented way to manipulate files. On a failure (like your permission issue) it throws Exceptions and if not handled this will result in a "hard" error with a backtrace log.
Awesome, thank you. I'm a bit rusty on best practices since I've shifted my focus away from php, but it's nice to know that options to make it behave a bit more high level exist.
At RubyConf last week, Matz (the creator of Ruby) introduced some of the ideas that are being tossed around for Ruby 2.0. One of these is called "refinements", and looks similar to what's being discussed in this article when it comes to method conflict resolution.
Shudo Maeda, a colleague of Matz, went into greater detail about Refinements in his talk. You can find the slides here: http://slidesha.re/9rbfwN
One of the noteworthy language additions are Traits – a brand new horizontal code reuse mechanism.
Code re-use mechanism? Horizontal? I didn't realize PHP was so geometric. What an interesting concept.
While traits are technically different from mixins – they are both designed to fill the same gap left by a single-parent inheritance model
So let me try and get this straight: mixins fill a gap left by a single-parent inheritance model and so traits are like mixins because PHP has a single-inheritance model but traits are technically different in some way?
This is getting confusing...
Python, Perl, Ruby, and many languages with multiple inheritance have been using mixins for... well a long time. It's pretty straight forward.
As the original author of the patch Stefan Marr pointed out – traits are nothing more but a compiler assisted copy and paste.
In true PHP fashion! What an interesting way to think about it.
Conflicts and Aliases
Ok, this is pretty cool. It's an edge case, but there's already a solution. Neat.
Oh... wait a second, I think I've found the meat of it:
Trait method definitions support all modifiers just like regular class methods do – that includes visibility modifiers, final, static and abstract. The latter can be used to define abstract methods expressing requirements for this particular trait
So this is how they're slightly different than regular-old classes? By being able to use all the same keywords as regular old classes?
It seems that the meat of it is at the very end:
Traits only take part in the process of building a class and do not add any new run-time semantics. That means that usual PHP object-oriented functionality (like late static bindings) work as expected when combined with traits.
But this next one is a bit of a gotcha:
Traits can be composed from other traits (the same way classes use traits).
Wait... what? Why would you want to do this? You're just getting back into hierarchies again... and pretty much just working around multiple-inheritance without calling it such.
It's good to see PHP is evolving. Most people still think of PHP4 and shudder (and rightfully so). Evolving means it might be catching up to the superior languages it competes with in the web space. (Note the tongue-in-cheek).
I'm still not convinced, but it's a step forward I am sure for PHP programmers everywhere. Just don't actually use trait-inheritance and you should be fine.
Multiple inheritance is available in languages like C++ and Python. But multiple inheritance has well known highly problematic side effects that resolve around diamond inheritance and method aliasing. Basically you have a directed graph of parent classes (with diamonds and all) all implementing a foo() method, and then have to pick a method of linearizing that graph to find out which method actually wins if someone calls o.foo(). There are multiple solutions to that, but all are non-intuitive and have caused heaps of trouble. It's just way to hard for a programmer to reason which method will be called in his program.
This is why Java did away with multiple inheritance and just has interfaces. A lot simpler, a lot cleaner, and easy to understand, but sadly lacking in functionality.
The problem is supposed to be fixed by traits and mixins. I think there is no really clean demarcation between the two. My understanding is that traits are more or less mixins, but with the aliasing features and similar stuff you highlight.
The traits are nothing more but a compiler assisted copy and paste is actually a feature, not a bug. This is exactly what you actually want to keep your program understandable: the code/functionality from traits is effectively treated as copied into your class, and there is exactly one "winner" in the race to be the "foo()" method for this class, where the winner is determined by things like aliasing, or order. All the weird situations where parent classes call a method that is overridden in some branch of the inheritance graph just don't occur.
So traits/mixins are IMHO a huge step forward from multiple inheritance or just interfaces. Not that this will help with the mess that is PHP much ...
Agreed. Also the conflict resolution is done in an interesting way. If you have 2 traits that have N conflicting methods, you have to rename all N methods in every single class using the traits (even if it doesn't use the methods in question).
Another interesting side effect is that even if you don't use a particular trait, but a conflict appears upstream - boom - "Warning: Trait method hello has not been applied, because there are collisions with other trait methods on TraitsTest in %s on line %d"
If I understand it correctly: You will be warned that a collision appeared, NONE of them will be applied and it's only a warning, so it won't stop the execution until you step on the "missing method" trap. Now that's a creative way to shoot yourself in the foot.
For the sake of readership (assuming that you understand why) this is because traits are compile-time but method calls can be done dynamically at run-time.
So when you say "even if it doesn't use the methods in question" -- well, the compiler has no way to know that.
The difference between mixins and traits is basically that traits happens at compile-time, where mixins are a run time feature. PHP is a bit odd in that it has static object model, where classes are defined as final entities at compile time, then used at run time. Traits hook in at the compile time level. This is unlike most comparable languages (Such as Ruby), where classes are run time constructs.
While odd, and somewhat limiting, there is also some benefit to the static object model that PHP has. It gives some point of certainty in a language that is otherwise very dynamic. In general, it leads to easier-to-reason-about code.
Ruby already does this. Modules can include other modules, support public/protected/private members, and have aliasing and conflict resolution (last one included wins)
> (last one included wins)
This is why Ruby doesn't do this.
One of the primary purposes of traits is to fix the problem of having to linearize mixins/parents. Aliasing does ease the problems of linearization, but it doesn't solve them all.
If in a mixin-based system like Ruby you include two modules which initially have no conflicting methods, but at some point methods are added such that there is a conflict, the conflict is silently ignored.
Yes, you should pay attention to changes to the mixins you use, but linearization means that whenever a method is added to a mixin, being certain that your module/class has the right method means going through every single class that uses the mixin and looking at all the other mixins that class uses to ensure that they do not conflict. Traits make changing code easier by doing that checking for you. The one disadvantage is that if you want to ignore a conflict you have to say so, but I don't actually think that is a disadvantage: if a Ruby module says "include Foo, Bar" and both Foo and Bar have an explode method, someone reading the code can't tell if the writer of the code was aware of the two explode methods and chose to use the Bar method, or if they were unaware of the conflict and might have either wanted no explode method or the Foo explode method.
While you're right about that point, there is a reason why conflicts are allowed: inheritance chain ([1] for demo) which provides you a VERY powerful tool for chain-combining traits.
great news indeed. we are developing a dynamic (HMVC) CRM system using Kohana3 and PHP 5.2, traits can really help use do our CRUD system a lot more dynamic, and probably other stuff too.
we are running the system on our own dedicated CentOS server ... and although we are developing with PHP 5.3, the most updated good package repository I've found is Jason Litka's repo, with PHP 5.2.x, he says he still got compatibly problems with the Zend Framework, so he won't update. so i guess using 5.4 on production will take some time. and i'm not talking about other virtual hosting providers who tend to update this things very slowly.
To me traits are actually a better solution than mixins via dynamism, since you get syntax checking via the compiler along with the benefit of compiled speed. You also have less confusion due to mixin conflicts.
I am super excited about this. Thanks PHP!