TLDR
The Drupal\Core\Extension\Extension
uses magic __call() to pretend that it is also the extension file.
This muddles concerns that should be separate, and makes the class more complex than it needs to be.
An extension is not conceptually identical with its *.info.yml file.
Current situation
The class Drupal\Core\Extension\Extension
was introduced in #2188661: Extension System, Part II: ExtensionDiscovery.
It contains one method Extension::__call(), which re-routes calls to SplFileInfo.
/**
* Re-routes method calls to SplFileInfo.
*
* Offers all SplFileInfo methods to consumers; e.g., $extension->getMTime().
*/
public function __call($method, array $args) {
if (!isset($this->splFileInfo)) {
$this->splFileInfo = new \SplFileInfo($this->pathname);
}
return call_user_func_array([$this->splFileInfo, $method], $args);
}
I claim that this is a bad idea.
As a reader, one immediately wonders: Which parts of \SplFileInfo
do we actually want to expose?
How can I find usages of those methods? The IDE won't help, because it's all magic.
We could declare the implicit methods as @method
tags.
I did this locally, and my IDE tells me that 2 of those are actually used, 22 are not used, and 4 are overwritten by an explicit implementation.
namespace Drupal\Core\Extension;
/**
* Defines an extension (file) object.
*
* Used:
* @method getMTime()
* @method getCTime()
*
* Overwritten by explicit method (IDE complains):
* @method getPath()
* @method getFilename()
* @method getPathname()
* @method getType() - meaning changed.
*
* Not used:
* @method getExtension()
* @method getBasename()
* @method getPerms()
* @method getInode()
* @method getSize()
* @method getOwner()
* @method getGroup()
* @method getATime()
* @method isWritable()
* @method isReadable()
* @method isExecutable()
* @method isFile()
* @method isDir()
* @method isLink()
* @method getLinkTarget()
* @method getRealPath()
* @method getFileInfo()
* @method getPathInfo()
* @method openFile()
* @method setFileClass()
* @method setInfoClass()
* @method __toString()
*/
class Extension implements \Serializable {
Problems
The added magic method Extension::__call()
adds potential complexity to the class, only so that a few places of consumer code can get the mtime and the ctime of the file.
It also pretends that the extension is identical with the file, which it shouldn't be.
It also introduces a huge BC commitment, if we consider the entire SplFileInfo as part of the public API of the class Extension.
I am sure there were reasons for this, but that doesn't mean it was a good idea, or inevitable.
Solution option I (hindsight)
The "correct" thing would have been to
- treat the SplFileInfo object and the respective Extension object as two separate things in the extension discovery.
- not add the magic __call()
- not add methods like getFilename() or getPathname() that mimick SplFileInfo.
- add a method Extension::getSplFileInfo() to get the SplFileInfo of the *.info.yml file.
This approach would cause major changes in the extension discovery, and possibly BC problems.
Solution option II
A less disruptive approach:
- add a method Extension::getSplFileInfo() to get the SplFileInfo of the *.info.yml file.
- add Extension::getCTime() and Extension::getMTime() for BC.
- remove magic Extension::__call()
This won't require major changes in core, and most contrib could still work as it is.
Strictly speaking it would still be a BC break.
If some strange contrib module uses some other parts of SplFileInfo via Extension::__call(), removing this would break.
Solution option III
The least disruptive approach:
Simply add the @method
docs at the top.
The "correct" thing here would be to add all of them, all methods that are not overridden.
And then additional notes about the 4 overridden methods.