MuIn the previous post, we had just started with porting the Flag module over to Drupal 8. We converted the *.info file to the new YAML file format. We also created a branch in the git repository on Drupal.org. At this point, the module is far from functional. While we can enable it, even visit the Admin > Structure > Flags page, creating a flag results in a massive 500 error on the server. Our next task is to find out why that happened, and do what we can to fix it.
So what went wrong?
We have a few ways of troubleshooting issues with Drupal. The first is to check the "Watchdog" logs under Admin > Reports > Recent Log Entries. While this is a good idea for most Drupal errors, it's useless when a 500 error is involved. Something has seriously gone wrong in that case. Instead, we need to check the webserver logs. Each webserver, and each OS, puts the logs in different places. I'm developing on Arch Linux, so I look under /var/logs/httpd.
The errors I found involve PHP not finding a class called "flag_flag". The Flag module, like many mature and complex modules, relies on several object-oriented classes in order to organize functionality internally. The problem is that as of Drupal 8, classes now have special significance.
"Phip, oop, drupe"
Since before I got involved with PHP over a decade ago, I knew Object Oriented Programming (OOP) was the way to go for many new projects. Each language I developed in had some form of objects. From C++, to Perl, Java, and even Python. PHP had objects too, but it was the strangest implementation I've ever encountered. Landmark features such as exception handling, interfaces, and so on were simply absent in PHP 4.x. Developing my own content manager in PHP 4.x at the time, was an exercise in language oddities. Shortly thereafter, I ditched my own content manager and started using Drupal instead. Nary an object did I find until the PDO layer in Drupal 7. In the meantime, PHP evolved from it's bizarre and incomplete OOP implementation to a proper one in 5.0, and a solid one in 5.3.
Meanwhile, Drupal remained stuck in its PHP 4 past.Drupal 8 is trying to change that. Larry Garfield made an insightful comment about what's happening. "The biggest change in Drupal 8 isn't Symfony, but PHP 5." The more I move forward with this module port, the more I'm astonished about how right he is.
In Soviet PHP, our PSRs are zero-indexed
One of the biggest changes is how PHP projects are trying to inter-operate in more ways than ever before. PHP Namespaces help to facilitate this goal, but that only goes so far. In addition to assuring that the class names do not collide between different projects, we need to assure that the filenames also do not collide. Java had this from it's earliest days, but PHP only recently confronted this problem. A standards committee was put together to combat the issue, and they created the PSR-0 Standard (pronounced "Zar-Zero").
The basic idea of PSR-0 is that when you create a class in PHP, it should conform to the following rules:
- Only one class may defined in one file.
- The filename and the classname must be the same with the *.php extension.
- Class files are put in the following directory structure: \VendorName\Namespace\Classname
- And one more rule that I'll get back to. Trust me, it's not an onerious as Directive 4 from Robocop.
Drupal 8 takes the first two as gospel. Rule three is slightly tweaked for the sake of modules. As we learned in the last post, modules are now installed in drupalRoot/modules. Classes for the module must be stored in the lib directory of the module directory. For Flag, classes need to be in drupalRoot/modules/flag/lib/VentorName/Namespace/Classname. For Drupal, the VendorName is always Drupal. Namespaces can be any level deep, but must be unique. Since module names must already be unique, it's natural to create the following directory structure drupalRoot/modules/flag/lib/Drupal/flag/Classname.
While Flag only has a handful of classes right now, there'll be much more in Drupal 8 as our porting effort moves forward. So instead, we put all of our classes in drupalRoot/modules/flag/lib/Drupal/flag/Handlers. We rename the file extension from *.inc to *.php, and add a namespace directive to each file.
After doing all of that, you'd think it'd just work, right. Nope.
DIRECTIVE 4: CLASSIFIED
I mentioned earlier that there's a forth rule about PSR-0 that I omitted. After moving and renaming all of the classes, they still wouldn't load. I turned to IRC, and everyone was suitably stumped. It wasn't until the next morning that I happened upon a detail that was omitted from every Drupal 8 how-to post I read.
- Each underscore (_) in the namespace name is converted to a DIRECTORY_SEPARATOR.
This means that if you name your namespace flag_namespace, PSR-0 will interpret it as flag/namespace and attempt to find the class file in a directory that does not exist. This is present in the standard because some frameworks -- most notably Zend -- use underscores as namespace separators. When I found this out, I took a hard look at my class and file names:
Note, that this detail is not in the PSR-0 Change Record for Drupal 8. I had to go back to the de facto PSR-0 page on Github to discover this fact. Instead of finding my classes under drupalRoot/modules/flag/lib/Drupal/flag/Handlers, it was looking under the non-existent directory of drupalRoot/modules/flag/lib/Drupal/flag/Handlers/flag. Furthermore, it was looking for files named flag.php user.php, comment.php, and node.php. As a result, I needed to rename all of my classes and class files to something more consistent with other OOP languages:
Much better! I suspect that many module maintainers will encounter similar problems as using heavily underscored class names was a standard practice in PHP 4.
Using namespaces in a world file forgot
In the previous post, we removed all of the file entries in our *.info file. This is actually because we now have PSR-0. In theory, any PSR-0 compatible class can be "autoloaded" by the PHP interpreter without needing to include it explicitly. One only needs to specify the fully qualified namespace when instantiating the class or calling any of its static methods. Flag module's AbstractFlag class (née flag_flag) is often used to create one of the other concrete flag classes using a factory method.
In Drupal 7:
In Drupal 8:
Yikes! That's a lot just to call one function! Thankfully, PHP's got ya covered. The slash-delimited part of the call above is the fully qualified path to the class. We can omit that part by using the use statement. At the beginning of any file in which you plan to use a class often, you can insert a use statement and the fully qualified path at the top of the file:
Then, later on in the file, you can omit the fully qualified path and just call the class name directly:
We've covered a lot in this post. We've moved all of our class files, created namespaces, and implemented the PSR-0 standard in Flag. Even after all of that, there's still a lot more work to be done. So far we've just set the stage for the real work that happens next.