Back in 2002 and php 4, you could not do things like this:
<?php $db = new Zend_Db();When the class Zend_Db is requested, for example to instantiate an object, its name is passed to an autoload function or method and its file included to give the class a chance to be defined before using it. If the autoloading process does not find a class, a fatal error is raised so it's not a little problem you can ignore by tuning the error_reporting level.
However, if you are not using autoloading the following code will be familiar:
<?php require_once 'Zend/Db.php'; $db = new Zend_Db();The require_once() call performs a manual inclusion of the Zend_Db class definition file, making Zend_Db available in all scopes. require_once() arguments are tipically relative paths from the list of directories defined in the include_path. Note that require_once() will not include the same physical file more than one time and that it will throw a fatal error too if it does not find the file. Since it is a language construct and not a function, parentheses are not needed.
In a typical project which does not rely on autoloading (such as Zend Framework and PHPUnit), every class file contains a list of require_once() calls at the top:
<?php /* [license...] */ /** Zend_Filter */ require_once 'Zend/Filter.php'; /** Zend_Validate_Interface */ require_once 'Zend/Validate/Interface.php'; /** * Zend_Form_Element * [docblock annotations...] */ class Zend_Form_Element implements Zend_Validate_Interface { ...This can be annoying: every time you reference a class you have to write its name, slightly modifying it to form the file name, and put a require_once() call at the top of the file you're working on. I'm sure there are automated tools for this process that scan the file and write require_once()s by themselves, but why adding more lines of code? Isn't maintaining less code better?
Of course it's better, unless the value the code adds to the application is worth the time for writing and maintain it. Otherwise there will never be new features in applications since no one would want to write new code.
The value provided here is declaration of dependencies. From the first part of Zend_Form_Element class file, I instantly learned that it has a dependency on Zend_Validate_Interface (obviously because this class implements it) and on Zend_Filter.
For example, these dependencies lists have been used for automatic generation of packages: you pick for example Zend_Form and a script packs all its dependencies in a zip you can download and decompress in your library folder. It would be great if it has no dependencies, but for a component to be useful a little coupling is mandatory.
During development, is always useful for current maintainers and for new programmers to learn the dependencies a class has. There is no need for static analysis tools in Zend Framework: simply open the file and see what is being included.
The problem is this methodology does not feature transitivity: Zend_Filter can be dependent on other classes and interfaces without them being listed in Zend/Form/Element.php; the inclusion of their files is placed in Zend/Filter.php and it's not a strange choice. Though, to test or develop a class you probably only need to know its direct dependencies (that's the usefulness of encapsulation) as they are classes which it communicates with by method calls and composition. In unit tests, for example, I'll mock out Zend_Filter and I won't care what it depends on.
Another problem is require_once() ties the dependencies to physical files. Unlike Java import statements, which references only a fully qualified class name such as Zend_Filter, we are including Zend/Filter.php; it is unlikely that this location will change in the future if the include_path is set correctly, but why saying something we do not intend? I really care only to state there is a dependency.
Php 5.3 can help us.
In an hypothetical version of Zend Framework with namespaces, the code of Zend\Form\Element will be as simple as:
<?php namespace Zend\Form; use Zend\Validate\ValidateInterface; use Zend\Filter;This form of collateral dependencies declaration was created to avoid name clashing. Its advantages are clear:
- it does not say anything on the file containing the referred class or interface;
- it is not needed for classes contained in the same namespace (like it was a Java package). Classes often depend on siblings and with require_once() statements we would be cluttering the list of external, important dependencies with the internal and obvious ones.
- it also shorts the names available for the collaborators: you can refer to Filter and ValidateInterface in the source file.
- it also declares what namespace/package the class belongs to, without recurring to docblock annotations.
Agreed, in my opinion namespaces and 'use' statements are the way to go. Many people forget that you can refer to any class in the same namespace without 'use'ing it and without writing the full class name (you mentioned that!). Also many forget that, unlike in Java, you can refer to classes in nested namespaces more easily. If you have a class A in namespace "One\Two\Three" and a class B in namespace "One\Two\Three\Four", you can refer to B as "Four\B" in class A. No need to fully qualify it or 'use' it.
ReplyDeleteI agree with 'use' statments being helpful for aggregating/visualizing dependencies that go beyond the current namespace.
I just wish there would be a bit more syntactic sugar for 'use'ing classes from a single namespace. For example instead of:
use One\Two\Three\Four\A;
use One\Two\Three\Four\B;
use One\Two\Three\Four\C;
Something like:
use One\Two\Three\Four\{A,B,C};
Thanks for the feedback Roman, I am more acquainted with php than with Java.
ReplyDeleteIn the last example, I guess it can remind us of not using a high number of classes from another namespace very often, since it increase coupling, and it does the job well by making it hard to write. :)