DrupalConDC Patterns and Object Orientation in Drupal Code
Drupal patterns
hook_menu, hook_theme, hook_views_data
In a sense, hook_menu and hook_form are declaritive programming.
Action hooks
hook_nodeapi, hook_user
See: passive observer
Use: Event-driven programming
Visitor hooks
hook_form_alter, hook_nodeapi, hook_user
Passive observer, Visitor
Use: Non-inheritance modification
Gets around the fact inheritance is one way.
Object "hook"
sort-of, kind of a way of doing inheritance. Couldn't figure out what this
Nodes
Same "object", different behavior based on data
Naked object
Use: Horizontal object extension pattern
See: State pattern
Very useful for adding things on.
All this applies to functional code as well as OO.
Render array
FAPI and derivatives
Use: UI toolkit
See: Visitor, Strategy patterns
Common OOP patterns
These aren't an exact science, these names are conventions but not declared by some on-high
Observer
Any hook is a passive observer pattern. Including module_invoke_all
Active observer pattern does not make sense in PHP. You'd have to register
CHX: Scince module depends on registry now, I suppose it's active now.
We build up a query
Crell: In a sense we are doing active observer. Still no OO code!
Someone who has read a
something happens, something else gets notified and does something in response
Visitor
This one is weird. Couldn't fit OO syntax for this on a single slide.
Any hook_node_alter is a visitor
have method
pass your state object to your action object which then does something to the state object
we already do it everywhere
every hook is a visitor pattern
Factory
There's not much Factory use in Drupal. Now in the database layer.
You want an object to talk to you in a certain way. So you tell the factory-- build me an object that meets this interface, I don't care which one.
Classic example-- pass the product, and the customer (so package size and weight, customer location)
Check out UPS, Post service
<?php
class MyFactory {
function getShipper($product) {
foreach ($this->shippers as $shipper) {
// in here, find the cheapest
}
}
}
$factory->getShipper($product, $customer);
$shipper->ship($product, $customer);
?>
This makes a lot more sense in OO. Interface in object-oriented code fits this model.
Chx: Fields in core uses this
Facade
Simpler, unified
In a sense, node_load is a facade.
Singleton
Incredibly important pattern to understand.
In a sense a facade for a global.
Global access to unique object.
Allows lazy-loading.
You always have that one object and can access it from anywhere.
<?php
class DB {
protected $instance;
private __construct() {} // private so you can never instatiate it yourself!
public static getInstance()
}
?>
deep inside db_query there is a Singleton doing this
Very simple and very easy to use and potentially dangerous.
Andy: Session a singleton?
Yes. Any global variable, $_
Objects a la Crell
"There is no problem in computer science that cannot be solved by another layer of indirection."
There are careers built on talking about object orientation. I won't go into that level of detail.
Just the above. (Except performance. Although even that sometimes you can help performance on a macro scale.)
Take a step back. Give yourself another level to work in.
- Powerful
- Sometimes expensive (sometimes cheap)
- Harder to read/debug
on the micro scale, more CPU cycles. On the macro, can intelligently bypass
Powerful but has conceptual overhead.
OO is syntactic indirection.
Easier to read than function indirection:
<?php
$foo->bar($a, $b, $c);
// vs
call_user_func_array($foo . '_bar', array($a, $b, $c, $last));
?>
There are more people who understand the above line than the lower-- than are involved with Drupal period. I didn't know about call_user_func_array until I got involved with Drupal.
If you know OO in Java, or even C++, it's close to OO in PHP. If you know OO in actionscript, easier.
There's a broader base of people
Cheaper if used wisely
Powerful.
Every hook theme call is a
I'm not saying replace every call_user_func_array with
Indirection is testable
- Mock objects
- Lie to your code
- Dependency injection
- higher setup cost in code
- much easier to reuse
- much easier to test
- Singletons are unmockable/untestable (something else somewhere may have played with the global... we have to get around this all the time in simpletest in drupal)
class God {
function hackCore() {
$kitten = new Kitten();
$lightning = new Lightning()
So you pass in a fake kitten when testing.
Singletons
-
anything shared that cannot be faked is a singleton
- Global variables
- Static variables
- Static methods
- Functions
Cannot be unit tested
Makes repurposing harder
PROBLEM: All functions are singletons. Every time you call db_query, variable_get, node_load.
Drupal is a
It's very easy to write spaghetti in OO code as well as functional code.
Drupal is a pile of
then you want to change one thing
OO Traps
Indirection requires a bridge. You need to know in advance that you need a kitten and a lightening bolt.
If you want to modify God to only kill kittens on Tuesday afternoons, you can't communicate that.
Whereas with a singleton, you can always call the function, get the global, and go.
So bad OO frameworks
Imagine a widget in CCK where you could never access the database. You could do a lot, but you could dig yourself into a deep hole.
You can always climb out of a hole with a singleton, but they have own issues.
If no object, no access.
But in PHP:
Object bootstrap more expensive than functions
Objects autoload (PHP will find it-- but with D7 registry we have already told it where to find everything) Avoid parsing code, and par
Stack calls not cheap
http://www.garfieldtech.com/blog/magic-benchmarks
In Java everything runs all the time, so the initialization time not so bad.
In PHP every request is a new
This is why most heavy OO frameworks, this can take a while (200 milliseconds) even if you have opcode cache.
Drupal right now is incredibly deep stack -- look at drupal_render in a debugger. (ooh, i have. yup. I'm so used to that in Drupal)
Good API design is about affordance (concept from product design to make things "easy and natural to do" -- like the doors in this room, has a push-bar and they push outward -- good.
Now we have hooks.
Hooks are awesome, hooks are great.
Hammers are for nails.
Screwdrivers are for screws.
If we use objects, we now have an approach that
Whither objects?
Singleton behavior: Function
Locally-shared data: Object
Easy indirection: Object and do dependency injection
Observer/Visitor: Hook (mostly, no reason to introduce objects for this)
Factory: Function returning objects - could do factories and objects. You could make a Factory Factory-- then you have Ruby on Rails. Don't over-engineer your OO code.
For most objects a function is sufficient.
Common problem, common approach, lowers barrier of entry for new programmers.
OOP Dos.
Always have an interface
easier for you to interface
makes it easier to hack your way out of a hole
I don't like how foo works, it's already bee
Don't overdo it on inheritance-- it is high on conceptual overhead. Go overboard, and you end up with Java.
Always type hint on an Interface (NOT on the class itself)
Makes it possible to hack your way out of a whole
Centralize "new"
- In the database. you are using objects all the time, and usually know this, but you done't have to type new. Ever.
- testability, maintainability, target optimization
Never be private
Unnecessary limitations. [Amen! Damn you, previous developer!]
Avoid statics
make code harder to understand and test
static == global == singleton
No inheritance in PHP < 5.3 (not out yet)
Beware over-engineering
OOP is a very big shovel (if procedural is a shovel, OOP is a backhoe)
Do not fear the arrow ->
An elegant weapon for a more civilized age
Further reading
Gang of Four
Design Patterns: Elements of Reusable Object-Oriented Software
Martin Fowler (one of the people who has written the most on the subject)
Never use a private variable?
they are there for a reason when you are doing super-high-level
private by definition means: I don't trust anyone.
We're drupal developers
protected is perfectly legitimate
because a
getters and setters like javabeans are over-engineered
PHPs magic methods for every class-- __get, __set, __call (for a method that doesn't exist)
Extenders in Drupal 7 database layer
Centralizing new: a good example is JQuery (even though JavaScript uses a different object module than PHP). The database layer in fact shares a lot of concepts with JQuery.
Q: Object orientation for dynamic loading of node data -- basic node data loaded, and then get the data when requested. Lazy loading.
Crell: I've been pushing for that for a year. I think that's a great idea, there are a lot of pitfalls to it.
Background on that:
http://groups.drupal.org/node/8001
Avoiding use of static variables? It's physically implemented as a global variable in PHP. There are good use cases for it. And there are dangers of digging
Remember to unset it.
Comments
Post new comment