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.
- See more at: http://www.acquia.com/blog/drupal-8-hello-oop-hello-world#sthash.Bf9eoinu.dpuf
相关推荐
The book also builds practical examples of common Drupal sites, such as a company website, a community website, and a commerce website, that you can take and expand on to create your own Drupal 8 ...
Successfully architect a Drupal 8 website that scales to meet project requirements of any size and scope. Starting with a one-chapter review of Drupal basics, you'll dive into deeper topics ...
Drupal 8 for Absolute Beginners is your definitive guide to starting from scratch with Drupal even if you have little web knowledge. This book teaches you the basics of HTML, CSS, JavaScript, and ...
Using Drupal 8 Explained, you can master Drupal 8 by using step-by-step examples. Drupal 8 is an amazingly powerful system, but many newcomers can find it confusing. We wrote this book to make Drupal ...
Using Drupal中文版 drupal中文资料 包括模板编辑 模块的使用以及案例的使用
using Drupal 中文翻译版 有两个章节没有翻译
Write a Drupal 8 module with custom functionality and hook into various extension points Master numerous Drupal 8 sub-systems and APIs Model, store, and manipulate data in various ways and for various...
Develop your programming skills by creating engaging websites using Drupal 8 About This Book Explore the new features of Drupal 8 through practical and interesting examples while building a fully ...
Drupal 8面向对象的编程基础本教程旨在帮助那些不熟悉面向对象编程(OOP)的人,并为Drupal 8开发所需的基础知识而苦苦挣扎。 与先前版本的Drupal的过程编程相比,Drupal 8是一个巨大的OOP转变。 本教程中的几乎所有...
包含:Drupal7宝典; Drupal开发指南; Using Drupal(强烈推荐) 值得你下载!
You will first learn how to set up and customize a basic blog using Drupal, one of the most powerful and popular content management systems available today. From there you will learn the basics of ...
Drupal 8 Blueprints Step along the creation of 7 professional-grade Drupal sites 英文azw3 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除
drupal8执行流程,以图示方式展示了drupal8的执行流程,对drupal8的架构一目了然
Drupal 8 Development Cookbook, drupal 8开发英文文档
教你如何使用drupal,是你入门的好帮手。
drupal,using drupal,PHP,源代码, 配套原书,不可多得
Using-DrupalUsing-DrupalUsing-DrupalUsing-DrupalUsing-DrupalUsing-DrupalUsing-Drupal
Drupal 8 Configuration Management is intended for people who use Drupal 8 to build websites, whether you are a hobbyist using Drupal for the first time, a long-time Drupal site builder, or a ...
The book also builds practical examples of common Drupal sites, such as a company website, a community website, and a commerce website, that you can take and expand on to create your own Drupal 8 ...
Part ll.Consuming and Manipulating Drupal8 10.Core REST 11.Using Views with Core REST 12.JSON APlin Drupal 13.RELAXed Web Services 14.GraphQL in Drupal Part IV.The Decoupled Drupal Ecosystem 15.AP-...