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
相关推荐
在 Drupal 这个强大的内容管理系统(CMS)中,"hello world" 模块通常被用作初学者入门的示例,它帮助开发者了解如何构建自己的 Drupal 模块。Drupal 是一个开源的平台,用于创建各种类型的网站,其强大的扩展性和...
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系统。虽然还有两个章节未完成翻译,但通过细致的准备工作和严谨的翻译流程,相信很快就能完善这一缺陷。对于想...
《 Beginning Drupal 8 中文版》是一本专为初学者设计的 Drupal 8 指南,旨在帮助读者快速掌握这一强大的开源内容管理系统。Drupal 8 是 Drupal 的一...配合提供的 begining-drupal8-cn.pdf 文件,学习过程将更加顺畅。
Drupal8是当前广泛使用的开源内容管理系统(CMS),它的架构和执行流程是基于 Symfony 框架构建的。Drupal8的执行流程包括了请求的接收、路由解析、控制器执行、渲染以及最终响应的返回。这一过程利用了Symfony ...
本书名为《Drupal 8 Development Cookbook - Second Edition》,是一本专注于 Drupal 8 开发的实用指南,由 Matt Glaman 撰写。Drupal 是一个功能强大的内容管理系统(CMS),它被广泛用于创建和管理网站内容。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...
《 Beginning Drupal 8》这本书是针对初学者的一本详尽指南,主要涵盖了Drupal 8的基础知识和实践应用。Drupal是一个开源的内容管理系统(CMS),广泛用于构建网站和应用程序。Drupal 8作为其最新版本,带来了许多...
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转变。 本教程中的几乎所有...
3. **Using Drupal**: 强烈推荐的这本书旨在教你如何有效地使用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删除
Drupal 8是流行的开源内容管理系统(CMS)之一,以其强大的功能和灵活性著称。配置管理是Drupal 8中的一个核心特性,它...通过阅读《Drupal8 Configuration Management》这份文档,你将能深入理解并充分利用这一功能。