Skip directly to content

Drupal 8: Hello OOP, Hello world!

on August 2nd, 2013 at 6:50:32 PM

Hello world. This is my first blog post. For those of you who don't know me, I'm effulgentsia on drupal.org. I work within Acquia's OCTO group, and I work a lot on Drupal core, both writing and reviewing patches.

In the course of working on Drupal 8 and attending various Drupal events, I've met quite a few Drupal 7 module developers curious about what they'll need to learn to be successful Drupal 8 module developers. Several people in the Drupal community have started writing blog posts about that, including one earlier this week by Joe Shindelar on writing a Hello World module.

In this post, I'd like to dive a little deeper into the first thing you'll probably notice if you watch that video and write the module: that you're now writing namespaced PHP classes instead of global functions, even for very simple stuff. If you first learned PHP within the last couple years, or have worked with any modern object-oriented PHP project, this might all be second nature to you. However, if you're like me, and only learned PHP in order to develop for Drupal 7 or earlier, it might take a bit to learn and adjust to the best practices of OOP in PHP. But hey, learning and adjusting is what programming is all about. Just look around at how much has changed in HTML, CSS, and JS best practices over the last 3 years. Why should the server-side stay stagnant?

To start with, let's look at what a hello.module would look like in Drupal 7:

hello.info

name = Hello
core = 7.x

hello.module

<?php

function hello_menu() {
  return array(
    'hello' => array(
      'title' => 'Hello',
      'page callback' => 'hello_page',
      'access callback' => 'user_access', 
      'access arguments' => array('access content'),
    ), 
  ); 
} 

function hello_page() { 
  return array( 
    '#type' => 'markup', 
    '#markup' => t('Hello.'), 
  ); 
} 

Pretty simple so far, right? There's a .info file that lets Drupal know about the module, and within the hello.module file, you implement hook_menu(), specify that you want a menu link (that shows up in your Navigation menu by default) at the URL "hello" whose link title is "Hello". Visiting that URL (whether by clicking that link or typing into the browser's address bar) should return the contents of the hello_page() function. But only to someone with "access content" permission.

However, there are two things here that should be improved even for Drupal 7. The first is that by having hello_page() in hello.module, PHP needs to load that function into memory for every single page request, even though most page requests will probably be for some URL other than /hello. One extra function to load isn't so bad, but on a site with a lot of modules, if every module did things this way, it would add up. So, it's better to move the function to a file that can be loaded only when needed:

hello.info

name = Hello 
core = 7.x 

hello.module

<?php

function hello_menu() {
  return array(
    'hello' => array( 
      'title' => 'Hello', 
      'page callback' => 'hello_page', 
      'access callback' => 'user_access', 
      'access arguments' => array('access content'), 
      'file' => 'hello.pages.inc', 
    ), 
  ); 
} 

hello.pages.inc

<?php

function hello_page() {
  return array(
    '#type' => 'markup', 
    '#markup' => t('Hello.'), 
  ); 
} 

The second problem is that there are no automated tests for this module. It is almost guaranteed that at some point, I will introduce bugs into this module. Or, if I contribute this module to drupal.org, that other people will submit patches to it that introduce bugs. Even very smart and diligent developers make mistakes. And frankly, I'm lazy. I don't want to have to manually test this module every time I make a change or review someone's patch. I'd rather have a machine do that for me. If I write tests, then drupal.org will automatically run them for every submitted patch that needs review, and if a test fails, automatically set the issue to "needs work" with a report of which test failed, all while I'm sleeping. So here's the module again, with a test:

hello.info

name = Hello 
core = 7.x 
files[] = hello.test 

hello.module

<?php

function hello_menu() {
  return array(
    'hello' => array( 
      'title' => 'Hello', 
      'page callback' => 'hello_page', 
      'access callback' => 'user_access', 
      'access arguments' => array('access content'), 
      'file' => 'hello.pages.inc', 
    ), 
  ); 
} 

hello.pages.inc

<?php

function hello_page() {
  return array(
    '#type' => 'markup', 
    '#markup' => t('Hello.'),
  ); 
} 

hello.test

<?php

class HelloTest extends DrupalWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'Hello functionality', 
      'group' => 'Hello', 
    ); 
  } 

  public function setUp() {
    parent::setUp('hello'); 
  } 

  public function testPage() { 
    $this->drupalGet('hello'); 
    $this->assertText('Hello.'); 
  } 

} 

So hey, how about that, if you're adding tests to your modules in Drupal 7, about half your code is already object-oriented! Ok, so what changes for Drupal 8? For starters, we're going to reorganize our hello.pages.inc file into a class. Which means, we need to pick a name for the class. Excuse the verbosity, but here's the name I'm going to pick: Drupal\hello\Controller\HelloController. What? Why so long? Here's why:

  • Before naming my module "hello", I first checked to see if that module name is already taken on drupal.org. I wouldn't want to write a module that conflicts with one already there. So great, I have a name that's available within the Drupal world. But what about the world at large? There are other open source PHP projects out there besides Drupal. What if people working on those projects discover my class and want to incorporate it into their project? Those projects might already have their own subcomponents named "hello". Including "Drupal" in the name ensures no conflict with those projects. You might be thinking, yeah right, what other project will find any value in my silly little class that just has one function that returns a Drupal render array? Ok, fair point, but maybe this will evolve into a more interesting module, where some of the classes really do solve some interesting problems in a generic way and are useful to other projects. I think that it's actually simpler (and more consistent) to just name all your classes as though they could be useful to other projects than to have to decide on a case by case basis whether to use a complete name or a short name.
  • After Drupal\hello\, I then added another Controller piece to the name. "Controller" is the "C" in MVC terminology, and within web frameworks, commonly refers to the top-level code that runs in response to a particular URL being requested. At some point, my module might grow to include a bunch more classes that have other responsibilities, so adding a "Controller" component to my name lets me group all my controller classes and keep them separate from non-controller classes.
  • The part of the name upto the final "\", Drupal\hello\Controller, is known in PHP as the namespace. Per PHP’s documentation, namespaces are conceptually similar to file directories in that they serve to group related items. Each part along the way is itself a namespace and serves to group, at each level becoming more specific. "Drupal" is a namespace, grouping all classes that are part of the Drupal project (in the broad sense, including core and contrib). "Drupal\hello" is a namespace, grouping all classes that are part of the "hello" module I’m creating for Drupal. "Drupal\hello\Controller" is a namespace, grouping all classes within this module that are controllers. Once I’ve reached the most specific namespace (group) that I consider to be useful, I still need to name the class itself that goes into this namespace. For now, I’m choosing HelloController, despite the fact that "hello" and "Controller" are already included in my namespace. When my module grows to include more pages, I'll probably want to organize them into multiple controller classes, and then I’ll be able to name each one in a meaningful way. But for now, I just have the one class and need to name it something, so I accept the redundancy.

Now that we have a fully namespaced class name, we need to decide the name of the file in which to put that class. For now, Drupal 8 requires (unless you want to write your own custom registration/loading code) the file name to match the class name as so: lib/Drupal/hello/Controller/HelloController.php. Yep, the file needs to be four levels deep within your module's directory. Here's why:

  • The lib directory is needed to organize your PHP classes separately from your other module files (YML files (more on that later), CSS files, etc.).
  • The Drupal/hello part of the directory path is needed to comply with PSR-0, a standard that says that the complete class name must be represented on the file system. This is an annoying standard, and the PHP standards group responsible for it are in the process of considering creating a new standard that will not require that. If they do so prior to accepting any other new standards, it will be named PSR-4. There is currently a Drupal core issue to switch to what will hopefully become PSR-4, at which time, we'll all be able to celebrate shallower directory paths within Drupal 8 modules.
  • The Controller directory is useful though. The whole point of adding it as a sub-namespace was to help organize the classes/files once the module grows to have many more classes.

Whew. Ok, with all that explained, here's the Drupal 8 version of hello.pages.inc:

lib/Drupal/hello/Controller/HelloController.php

<?php

namespace Drupal\hello\Controller;

class HelloController {
  public function content() {
    return array(
      '#type' => 'markup', 
      '#markup' => t('Hello.'), 
    ); 
  } 
} 

If you watched the video in Joe's post, you’ll see that there, he enhanced the HelloController class a bit to implement a Drupal core provided interface, ControllerInterface. Doing that is not necessary for very simple controllers like this one that don’t call any outside code. It does, however, allow for one of OOP’s coolest features: dependency injection. But let’s leave a deep dive into interfaces and dependency injection to a future blog post. Also, as covered in that post, in Drupal 8, the .info file changed to .info.yml, and parts of hook_menu() are now in a .routing.yml file. So adding those in, the entire module (without tests) becomes:

hello.info.yml

name: Hello 
core: 8.x 
type: module 

hello.routing.yml

hello: 
  path: '/hello' 
  defaults: 
    _content: '\Drupal\hello\Controller\HelloController::content' 
  requirements: 
    _permission: 'access content' 

hello.module

<?php

function hello_menu() {
  return array(
    'hello' => array( 
      'title' => 'Hello', 
      'route_name' => 'hello', 
    ), 
  ); 
} 

lib/Drupal/hello/Controller/HelloController.php

<?php

namespace Drupal\hello\Controller;

class HelloController {
  public function content() {
    return array(
      '#type' => 'markup', 
      '#markup' => t('Hello.'), 
    ); 
  } 
} 

As far as the tests go, those classes also need to be namespaced, be in PSR-0 compatible file names (which also means one class per file, not all lumped into a single .test file), and there are some other changes for porting tests to Drupal 8, which you can read about in these change notices. So, there you have it. To sum up:

  • A lot of your module code will now be in classes. Possibly not all (for example, hook implementations like hello_menu() can still be functions in the .module file), but most. This sets the stage for defining interfaces, type hinting, injecting dependencies, and other best practice OOP techniques for making software more maintainable, testable, robust, and scalable. Stay tuned for future blog posts covering each of these topics.
  • The classes should be within a namespace that starts with Drupal\YOUR_MODULE_NAME. This allows for your classes to not conflict with any other code in any other Drupal module, and even any other code outside of Drupal. While you might not think that other projects will want to use your classes, don’t completely write off that possibility. After all, open source is all about sharing, so why not open the door to sharing more broadly!
  • Within that namespace, you can create more sub-namespaces, as desired to achieve your ideal level of organization.
  • Each class goes into its own file, with the directory structure matching the namespace structure.

Thanks for reading my very first blog post ever. I hope you found something useful in it and in the ones to come!

Comments

Ryan's picture

Thanks for the tutorial, Alex - very helpful.

Aaron's picture

Thanks for this explanation. Given the information from this post, hook_menu and hello.routing.yml seem almost totally redundant. Will your future posts also explain why both these are still necessary?

effulgentsia's picture

Hi Aaron,

In Drupal 7, hook_menu() did two things in one: it defined what happens when you visit a URL (e.g., the 'page callback' key) and it added a link to that URL in an appropriate menu (e.g., the 'title' key provides the title of that link). In Drupal 8, the .routing.yml now handles the first part, and hook_menu() is only for the second part. Take another look at the code snippets in the post, and notice that the D8 hello_menu() is shorter than the D7 one.

Douglas Urner's picture

Thank you very much for the example and explanation – I'm really looking forward to any future installments!

Got a question about the difference between hook_menu() in D7 and D8. Aside from the routing functionality in D7, hook_menu() also caused the menu item to be added to the Navigation menu. This doesn't appear to happen in D8. Is that by intention? If so, could you describe how to add the item to a menu in D8?

Joaquin Bravo Contreras's picture

So if the hook_menu is just for the menu link, would this example work without the hook_menu? Would it work without the .module file at all?

effulgentsia's picture

Yes, you can remove the hook_menu() implementation, and the routes declared in the .routing.yml file still work; you just don't get links for them showing up in any menus, but you can output links to them from your own custom code.

The .module file is still required at this time, but it can be empty. There's an open issue to remove that requirement.

Agnar Ødegård's picture

As a D7 developer, I really welcome posts like this explaining things bottom up. Looking forward to your future posts!

Ranel's picture

well-written article, thumbs-up. :)

Matthew Slater's picture

thanks to angie for linking here. Why don't you make an rss feed? Otherwise how am I going to catch the rest of the series?

Miklós Kasza's picture

FYI, RSS-feed can be accessed here: http://effulgentsia.drupalgardens.com/blog/feed

Chris Trahey's picture

I want to take a moment and advocate for re-thinking namespaces like your samples. What's a polite way for me to say that your example represents a failure (not unique to this post) of the PHP community (and the Symfony & Drupal communities in particular) to grasp the appropriate use of namespaces? First and foremost: NOBODY but core developers should start their namespace with \Drupal. I hope my reasoning is obvious now that I've called it out, but I'm happy to clarify in followup comments. Second, please reconsider your nonchalant dismissal of the redundancy in the namespace/classname. This is happening in the Symfony community and I think it's short-sighted and reflective of a lack of trust or familiarity with what namespaces are here to do for us. Let me give you an example of the kind of namespacing I am advocating: 

namespace \Trahey\hello

and then the class name would end up as

\Trahey\hello\Controller

If I have more controllers later, they can be disambiguated in a semantic way that does not require redundancy: 

\Trahey\hello\Auth\Controller// or simply\Trahey\hello\AuthController

 There is more nuance to why I think this is an important issue. For related reading see this blog post  I wrote last fall when I first started using Symfony and developed this opinion: http://www.metaltoad.com/blog/improving-our-use-namespaces

Vahid's picture

Great! I like object oriented.

John_B's picture

This is fantastically useful. Thank you for taking the time to do it.

But why is Drupal going this way? When you install D8 alpha as a site builder it looks pretty much like a polished version of D7. Under the surface it has changed a lot more than would be necessary just to add that (welcome) polish.

I make a living from maintenance and minor dev on more or less badly made Drupal sites for SMEs who lost faith in their original devs, and cannot afford the big Drupal shops. For smaller sites I use Wordpress, for app building (which is above my pay grade) it may not be clear that Drupal is the best tool (while reading this blog I also read this interesting post http://blog.varunarora.com/2013/why-we-stopped-using-drupal-for-our-plat...). For my clients Drupal does what they need well, but maintenance is an expensive headache. Will the changes in the underlying code make a D8 site with a smorgasbord of contrib modules less expensive to maintain (which is what my clients really need) or more? Is it about trying to wrest a decent CMS into a highly complex framework for apps? I study Drupal every day and contribute in my way (daily support on the forums for mainly unskilled users who picked Drupal over Wordpress because they heard it is better, and are often way out of their depth: check my posts tracker to see their pain). Will these changes help them? I am not really getting the motivation for the changes in D8.

highermath's picture

Drupal is going this way because it is built by a community of developers, for developers. It won't mean much to folks who want to build a simple blog or even a showcase site, because they will want to work with Drupal's site-building tools, not the code. Many, if not most/all professional developers like MVC because although it adds some complexity in a small project, it actually reduces it in a large project, and Drupal — even if you just use core — is a large project. In the case of Drupal, this added complexity is the Symfony framework. IMHO, it is a great move. While it is true that contrib module developers will need to learn Symfony, just as they have had to learn earlier coding standards and tools, it will make their modules better in terms of integration and compatibility. The more routines you can grab from the framework, the shorter the path to a successful project. Of course, this really has no direct impact on the site-building process. I think that that is really more a matter of having sufficient profiles and/or distributions to get folks headed in the right direction. To be honest, though, at the end of the day, some folks will be better off with Wordpress. Personally, I like building stuff and I would much rather have a great framework for building sites, services and applications than a decent CMS.

gauravjeet_singh's picture

very nice article thanks..

danylevskyi's picture

Thank you very much for this great post! It's really interesting.
You definitely should continue!

Rimu's picture

Woo, that's a lot of extra complexity for not much gain. Looks like D7 will be my last drupal iteration.

Ben's picture

Or otherwise, "I don't want to learn to do things properly so I'm leaving".

Wes's picture

The "proper" way to do things is a hotly debated topic that is constantly changing. Just look at the Drupal core issue queue my dear friend.

Clivesj's picture

Thanx for the nice post. Will you follow-up on Symfony. The question is how much Symfony do we have to learn?

Shimju David's picture

HI there,
I loved this article and though Iam new to Drupal and its module development things, being an OOP developer, I can easily follow it. But Now I would love to see much more advanced module with CRUD functionality- say a contact manager module:) that will definitly going to explain a lot more in detail. Once again, D8's move towards OOP is really welcoming for developers like me.. thank you again and expecting more D8 stuffs from your end.

Ivan's picture

I want to translate this article to spanish and publish it in drupalmexico.com with credits and link to original text.

Will be possible to do this?

effulgentsia's picture

I don't know what the terms of drupalmexico.com are, but I have no objection to you or anyone else posting translations of this to any site that allows it.

drupaul's picture

Fantastic, I hadn't realised so much of Symfony's approach would filter through to D8 module development so early! Great job!

I'm sure a lot of burgeoning PHP developers such as myself are clambering towards OOP and feel a bit crappy (and if I'm honest embarrassed) that they don't have more experience with it.

I was really questioning whether I wanted to continue contracting as a Drupal developer or re-focus to Symfony. I love Drupal, it's a great CMS and I've built my very happy career on it but was starting to get a bit frustrated with procedural code and non-framework feel.

I feel like this change allows me to continue developing with Drupal, where my expertise is, while also ensuring I'm keeping up with modern development practice, MVC concepts and Symfony.

Big thumbs up from me.

eojthebrave's picture

Thanks effulgentsia, this is great and I think really clarifies some of the things that I was struggling to explain when writing a simple hello world module. As someone who learned to program PHP via Drupal and who is just starting to delve into the these new Controllers in D8 this is invaluable knowledge. And exactly the kind of information that we need to continue to get out in the wild.

I'm feeling compelled to record a follow up video that explains that my version was overly complicated and why. :)

Ben's picture

I've been building modules for Drupal 7 in a similar way for a while (minus the routing etc obviously). I generally have a .module file which does nothing but invoke hooks, any actuall processing goes on in a class file bundled with the module. Has a whole heap of OOP benefits and keeps stuff more modular too.

Also, I had no idea so few Drupal developers know OOP principles.

effulgentsia's picture

Cool that you've already been applying OOP in your Drupal 7 modules!

I disagree with your statement that "few Drupal developers know OOP principles". There are very many who do. It's just that Drupal has a HUGE developer community, probably one of the largest of any software project out there. So there are also very many who don't. At least not yet. But I'm confident that anyone who's open to learning them, can.

tom stovall's picture

#type => markup is deprecated in d8

https://drupal.org/node/2036237

David Kinzer's picture

Very informative article. Thanks for taking the time to write and share it.

Digital Agency's picture

Very useful article. I really appreciate people who share their knowledge in terms of Drupal development not only on drupal.org but also on their blogs. Congratulations with your first blog post.

Chris Shattuck's picture

I just wanted to say thanks for writing this up and that I love that this is your first blog post ever. Great way to start. :)

gauravjeet's picture

really awesome tutorial. thanks !!

Tony's picture

We all know how that story went. Drupal should have stuck to its core and not copy so heavily from Symfony. Now you have a hybrid mix of crap that really would confuse the end developers. Wordpress continues to be simple and Drupal continues to be a mess. Hey core developers, how about a novel idea like backwards compatibility work for you all? Everyone else seems to have it.

Ana Vulcan's picture

Thank you very much for explaining the structure of the folders in Drupal. I just couldn't wrap my head around it before, but this article sheds some light on that particular issue.

Ruben Teijeiro's picture

Hi,

Thanks for the post. It inspired me to write this module: https://drupal.org/sandbox/rteijeiro/2087957

One thing, you should update the post according to: https://drupal.org/node/2089727 ;)

See you in the issue queue :P

effulgentsia's picture

Thanks Ruben. I updated the code example for the change of key name from 'pattern' to 'path'. I'm glad this inspired you to play with your own sandbox project, and thanks for writing up your experience.

liquidcms's picture

Anyone remember that Drupal catch-phrase? "right up there with God kills a kitten every time you hack core". "There is likely a module for that" is one of the main reasons Drupal has become the best CMS out there. But the comments above that state there are lots of Drupal devs that do know OOP along side the comment that there are a lot that do not... is pretty funny. Realistically I would guess the ratio is likely around 10 to 1 (those that do not to those that do). What this will mean is that the 4000+ modules currently available for D7 will dwindle to 400+ modules for D8 (let's say 1 year after D8 release). I am not saying there won't be a day when the module count will rival that of D7; but my guess (and I have been right each time I have given a much more pessimistic "when it is good to switch" to next D release going to back to D4) is that the "real" date for "good to go" for D8 is likely to be closer to late 2015 or early 2016... for that 1 simple reason.. there likely won't be a module for that (anytime soon).

The Drupal elite, Acquia, Dries, etc are targeting Drupal for (as someone above wrote) the large complex web sites. Large complex likely being in the $1/4M+ mark or the vast minority of projects... but the only ones Acquia is interested in. For these projects, with excessive development budgets; the fact that there likely isn't a module for that is likely not an issue since they will be developing most sites using tons of custom code.. which, I admit is likely better written with the aid of OOP. But the days of doing reasonably complex projects with Drupal where you could count on 1000s of community developed modules to do almost every little task; and then back-fill the few features that were missing with a couple simple custom modules is likely to die with D7.

Long live BackDrop (http://backdropcms.org)!! :)

effulgentsia's picture

I agree with you that the breadth of contrib modules and Drupal's large developer community are Drupal's greatest strengths.

I think your guess that less than 10% of that community can work with OOP is too pessimistic. Even if it's true that 90% have not yet encountered OOP in any of their other development work, this stuff can be learned. If someone can go from no prior programming experience at all to learning PHP, SQL, and Drupal (as many Drupal developers have done), they have already jumped over a bigger hurdle than the transition to OOP.

Regarding the time delay from when an X.0 version of core is released to when a comparable set of contrib modules is available for it as are available with the X-1 version, it took longer than a year for that to happen with both D6 and D7. However, a large part of that was the time it took very large modules (e.g., Views) that everyone relies on to become ready. With a lot of those now in D8 core, I think there's a really good chance of D8 modules becoming ready much sooner. So my prediction is late 2014 or early 2015, but time will tell which of us is closer.

I also disagree with your assumption about the motivations of the Drupal elite, Acquia, and Dries. If by the Drupal elite, you mean, say, the 100 or so most active core and contrib developers and their employers/clients, then that's a pretty diverse mix of people and organizations, many of whom work on sites considerably smaller than $1M. At Acquia, most of our Network subscription, Cloud hosting, and other customers have smaller websites than that as well, so it's certainly not in our business interest for Drupal to become a platform that is limited to only large complex sites. As to Dries, I think his vision of Drupal is best expressed in his One Drupal to rule them all post, where he points out that Drupal's strength is in its ability to be a good fit for a large range of sites.

Jayesh's picture

Its very good example. 

Joe's picture

Thanks for the really clear and informative post. As a manager of developers working on CiviCRM and Drupal mainly, I found it quite useful. FWIW, CiviCRM has also adopted Symfony for its extensions and is gradually incorporating more of it into CiviCRM core. Looking forward to your future posts, hopefully including a CRUD example. Very timely given the resistance to D8 that is causing the Drupal fork.

effulgentsia's picture

Thanks Joe. In case you missed it, I did post a CRD example (no U).

plaon.com's picture

Thanks for a nice article! Some correction: please, replace ControllerInterface to ContainerInjectionInterface according to https://drupal.org/node/2079895

latetotheparty's picture

Thanks for the toot. This looks sort of like Laravel with the routing and such. I'm assuming that only the routing from hook_menu will be busted out into a separate yml file from the .module file. Is this true or will other hooks be "broken up" into separate files from the .module file? Also, what will install scripts look like?

Post new comment