Na navigaci | Klávesové zkratky

Best Practices for Namespaces in PHP

Here are some well-intentioned tips on how to design the structure of namespaces and class names.

Namespaces are probably the best-known new feature of PHP version 5.3. Their main purpose is to prevent name conflicts and to allow shortening (aliasing) of class names for use within a single file. In practice, it has been shown that conflicts can also be avoided by using a 1–2 letter prefix, just as I have never used class names like Zend_Service_DeveloperGarden_Response_ConferenceCall_AddConferenceTemplateParticipantResponseType (97 characters, I wonder how they adhere to their maximum line length rule of 80 characters 🙂 ). However, PHP follows in the footsteps of Java, and so we have namespaces. How should we handle them?

Benefits of Namespaces

Perhaps the most complex question you need to answer is: what is the benefit of renaming a class:

  • sfFormSymfony\Component\Form\Form

This question is a proven starter for endless flame wars. From the programmer's comfort, intuitiveness, and memorability perspective, the original concise and descriptive sfForm is more appropriate. It corresponds to how programmers colloquially refer to it, i.e., “form in Symfony”. The new and longer name is correct from other aspects, which I am not sure if the average user will appreciate.

How to Layout Namespaces?

The syntactic aspect of using namespaces is described in the documentation, but finding the right patterns requires practice, which there hasn’t been enough time for yet. Spaces in PHP have their specifics due to a number of factors, so it is not ideal to copy conventions used in Java or .NET exactly. However, they can be a good starting point.

More will be discussed in the individual naming rules.

1) A class should have a descriptive name even without mentioning the NS

The name of each class, even without the namespace, must capture its essence. It would be inappropriate to rename the class ArrayIteratorSpl\Iterators\Array, as one would not expect an iterator under the name Array (ignoring the fact that a class cannot be named a keyword). And beware, even from the name Spl\Iterators\Array, it is not clear that it is an iterator, because you cannot assume that the namespace Spl\Iterators only contains iterators. Here are a few examples:

  • unsuitable: Nette\Application\Responses\Download – it is not obvious that Download is a response
  • unsuitable: Zend\Validator\Date – you would expect Date to be a date, not a validator
  • unsuitable: Zend\Controller\Request\Http – you would expect Http to be a request

Therefore, in addition to specializing classes, it is appropriate to keep a level of generality in the name:

  • better: Nette\Application\Responses\DownloadResponse
  • better: Zend\Validator\DateValidator
  • better: Zend\Controller\Request\HttpRequest

The ideal is if there is a one-word yet descriptive name. This can be particularly conceived for classes that represent something from the real world:

  • best: Nette\Forms\Controls\Button – two-word ButtonControl not necessary (however, HiddenControl cannot be shortened to Hidden)

2) The namespace should have a descriptive name

Naturally, the name of the namespace itself must be descriptive, and it is advantageous to have a shorter name without redundancies. Such a redundancy to me seems like Component in Symfony\Component\Routing, because the name would not suffer without it.

In some situations, you need to decide between singular and plural (e.g., Zend\Validator vs Zend\Validators), which is a similarly undecided issue as when choosing singular and plural numbers for database tables.

3) Distinguish between namespaces and classes

Naming a class the same as a namespace (i.e., having classes Nette\Application and Nette\Application\Request) is technically possible, but it might confuse programmers and it is better to avoid it. Also, consider how well the resulting code will read or how you would explain the API to someone.

4) Limit unnecessary duplications (+ partial namespace)

Ideally, the name of the class and the name of the space should not contain the same information redundantly.

  • instead of Nette\Http\HttpRequest prefer Nette\Http\Request
  • instead of Symfony\Component\Security\Authentication\AuthenticationTrustResolver prefer the class TrustResolver

The class Nette\Http\Request does not violate rule No. 1 about the descriptive name of the class even without mentioning the namespace, on the contrary, it allows us to elegantly use the partial namespace:

use Nette\Http; // alias for namespace

// all classes via Http are available:
$request = new Http\Request;
$response = new Http\Response;
// and additionally, Http\Response is more understandable than just Response

If we understand namespaces as packages, which is common, it leads to unfortunate duplication of the last word:

  • Zend\Form\Form
  • Symfony\Component\Finder\Finder
  • Nette\Application\Application

Namespaces also literally encourage grouping classes (e.g., various implementations of the same interface, etc.) into their own spaces, which again creates duplications:

  • Nette\Caching\Storages\FileStorage – i.e., all storages in a separate space Storages
  • Zend\Form\Exception\BadMethodCallException – all exceptions in Exception
  • Symfony\Component\Validator\Exception\BadMethodCallException – again all exceptions in Exception

Grouping namespaces lengthen the name and create duplication in it because it is often impossible to remove the generality from the class name (rule 1). Their advantage may be better orientation in the generated API documentation (although this could be achieved differently) and easier access when using full-fledged IDEs with prompting. However, I recommend using them cautiously. For example, for exceptions, it is not very suitable.

5) Unmistakable classes from multiple spaces

According to point 1), a class should have a descriptive name, but that does not mean it has to be unique within the entire application. Usually, it is enough that it is unique within the namespace. However, if two classes from different spaces are often used next to each other in the code, or if they have some other significant connection, they should not have the same name. In other words, it should not be necessary to use AS in the USE clause.

6) One-way dependencies

Consider what dependencies should exist between classes from different namespaces. I try to maintain:

  • if a class from the namespace A\B has a dependency on a class from the namespace A\C, no class from A\C should have a dependency on A\B
  • classes from the namespace A\B should not have dependencies on a class from the space A\B\C (take this with a grain of salt)

p.s.: Please do not take this article as dogma, it is just a capture of current thoughts

You might be interested in


phpFashion © 2004, 2024 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.