Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I see two things here.</p> <p>The first one makes your problem a bit complicated. You want to make use of namespaces, but your current configuration is via the file-system. The file-names of the class definition files does not contain the namespace so far. So you can not just continue as you actually do.</p> <p>The second is that you do not have what's covered by PHP autoloading, you just load a defined set of classes and register it with the registry.</p> <p>I'm not really sure if you need PHP autoloading here. Sure it might look promising for you to bring both together. Solving the first point will probably help you to solve the later, so I suggest to start with it first.</p> <p>Let's make the hidden dependencies more visible. In your current design you have got three things:</p> <ol> <li>The name under which an object is registered in the registry.</li> <li>The filename which contains the class definition.</li> <li>The name of the class itself.</li> </ol> <p>The values of 2. and 3. are in one, you parse the name of the class itself from the filename. As written, namespaces make this complicated now. The solution is easy, instead of reading from a directory listing, you can read from a file that contains this information. A lightweight configuration file format is json:</p> <pre><code>{ "Service": { "file": "test.class.php", "class": "Library\\Of\\Something\\ConcreteService" } } </code></pre> <p>This contains now the three needed dependencies to register a class by a name into the registry because the filename is known as well.</p> <p>You then allow to register classes in the registry:</p> <pre><code>class Registry { public function registerClass($name, $class) { $this-&gt;$name = new $class($this); } } </code></pre> <p>And add a loader class for the json format:</p> <pre><code>interface Register { public function register(Registry $registry); } class JsonClassmapLoader implements Register { private $file; public function __construct($file) { $this-&gt;file = $file; } public function register(Registry $registry) { $definitions = $this-&gt;loadDefinitionsFromFile(); foreach ($definitions as $name =&gt; $definition) { $class = $definition-&gt;class; $path = dirname($this-&gt;file) . '/' . $definition-&gt;file; $this-&gt;define($class, $path); $registry-&gt;registerClass($name, $class); } } protected function define($class, $path) { if (!class_exists($class)) { require($path); } } protected function loadDefinitionsFromFile() { $json = file_get_contents($this-&gt;file); return json_decode($json); } } </code></pre> <p>There is not much magic here, file-names in the json file are relative to the directory of it. If a class is not yet defined (here with triggering PHP autoloading), the file of the class is being required. After that is done, the class is registered by it's name:</p> <pre><code>$registry = new Registry(); $json = new JsonClassmapLoader('path/registry.json'); $json-&gt;register($registry); echo $registry-&gt;Service-&gt;invoke(); # Done. </code></pre> <p>This example as well is pretty straight forward and it works. So the first problem is solved.</p> <p>The second problem is the autoloading. This current variant and your previous system did hide something else, too. There are two central things to do. The one is to actually load class definitions and the other is to instantiate the object.</p> <p>In your original example, autoloading technically was not necessary because the moment an object is registered within the registry, it is instantiate as well. You do this to assign the registry to it as well. I don't know if you do it only because of that or if this just happened that way to you. You write in your question that you need that.</p> <p>So if you want to bring autoloading into your registry (or lazy loading), this will vary a bit. As your design is already screwed, let's continue to add more magic on top. You want to defer the instantiation of a registry component to the moment it's used the first time.</p> <p>As in the registry the name of the component is more important than it's actual type, this is already pretty dynamic and a string only. To defer component creation, the class is not created when registered but when accessed. That is possible by making use of the <code>__get</code> function which requires a new type of Registry:</p> <pre><code>class LazyRegistry extends Registry { private $defines = []; public function registerClass($name, $class) { $this-&gt;defines[$name] = $class; } public function __get($name) { $class = $this-&gt;defines[$name]; return $this-&gt;$name = new $class($this); } } </code></pre> <p>The usage example again is quite the same, however, the type of the registry has changed:</p> <pre><code>$registry = new LazyRegistry(); $json = new JsonClassmapLoader('path/registry.json'); $json-&gt;register($registry); echo $registry-&gt;Service-&gt;invoke(); # Done. </code></pre> <p>So now the creation of the concrete service objects has been deferred until first accessed. However this still yet is not autoloading. The loading of the class definitions is already done inside the json loader. It would not be consequent to already make verything dynamic and magic, but not that. We need an autoloader for each class that should kick in in the moment the objects is accessed the first time. E.g. we actually want to be able to have rotten code in the application that could stay there forever unnoticed because we don't care if it is used or not. But we don't want to load it into memory then.</p> <p>For autoloading you should be aware of <a href="http://php.net/spl_autoload_register"><code>spl_autoload_register</code></a> which allows you to have more than one autoloader function. There are many reasons why this is generally useful (e.g. imagine you make use of third-party packages), however this dynamic magic box called <code>Registry</code> of yours, it's just the perfect tool for the job. A straight forward solution (and not doing any premature optimization) is to register one autoloader function for each class we have in the registry definition. This then needs a new type of loader and the autoloader function is just two lines of code or so:</p> <pre><code>class LazyJsonClassmapLoader extends JsonClassmapLoader { protected function define($class, $path) { $autoloader = function ($classname) use ($class, $path) { if ($classname === $class) { require($path); } }; spl_autoload_register($autoloader); } } </code></pre> <p>The usage example again didn't change much, just the type of the loader:</p> <pre><code>$registry = new LazyRegistry(); $json = new LazyJsonClassmapLoader('path/registry.json'); $json-&gt;register($registry); echo $registry-&gt;Service-&gt;invoke(); # Done. </code></pre> <p>Now you can be lazy as hell. And that would mean, to actually change the code again. Because you want to remote the necessity to actually put those files into that specific directory. Ah wait, that is what you asked for, so we leave it here.</p> <p>Otherwise consider to configure the registry with callables that would return the instance on first access. That does normally make things more flexible. Autoloading is - as shown - independent to that if you actually can leave your directory based approach, you don't care any longer where the code is packaged in concrete (http://www.getcomposer.org/).</p> <p>The whole code-example in full (without <code>registry.json</code> and <code>test.class.php</code>):</p> <pre><code>class Registry { public function registerClass($name, $class) { $this-&gt;$name = new $class($this); } } class LazyRegistry extends Registry { private $defines = []; public function registerClass($name, $class) { $this-&gt;defines[$name] = $class; } public function __get($name) { $class = $this-&gt;defines[$name]; return $this-&gt;$name = new $class($this); } } interface Register { public function register(Registry $registry); } class JsonClassmapLoader implements Register { private $file; public function __construct($file) { $this-&gt;file = $file; } public function register(Registry $registry) { $definitions = $this-&gt;loadDefinitionsFromFile(); foreach ($definitions as $name =&gt; $definition) { $class = $definition-&gt;class; $path = dirname($this-&gt;file) . '/' . $definition-&gt;file; $this-&gt;define($class, $path); $registry-&gt;registerClass($name, $class); } } protected function define($class, $path) { if (!class_exists($class)) { require($path); } } protected function loadDefinitionsFromFile() { $json = file_get_contents($this-&gt;file); return json_decode($json); } } class LazyJsonClassmapLoader extends JsonClassmapLoader { protected function define($class, $path) { $autoloader = function ($classname) use ($class, $path) { if ($classname === $class) { require($path); } }; spl_autoload_register($autoloader); } } $registry = new LazyRegistry(); $json = new LazyJsonClassmapLoader('path/registry.json'); $json-&gt;register($registry); echo $registry-&gt;Service-&gt;invoke(); # Done. </code></pre> <p>I hope this is helpful, however this is mainly playing in the sandbox and you will crush that the sooner or later. What you're actually want to learn about is Inversion of Control, Dependency Injection and then about Dependency Injection containers.</p> <p>The Registry you have is some sort of smell. It's all totally full of magic and dynamic. You might think this is cool for development or for having "plugins" in your system (it's easy to extend), however you should keep the amount of objects therein low.</p> <p>Magic can be hard to debug, so you might want to check the format of the json file if it makes sense in your case first to prevent first-hand configuration issues.</p> <p>Also consider that the registry object passed to each constructor is not one parameter but represents a dynamic amount of parameters. This will start to create side-effects the sooner or later. If you are using the registry too much, then more the sooner. These kind of side-effects will cost you maintenance a lot because by design this is already flawed, so you can only control it with hard work, heavy integration tests for the regressions etc..</p> <p>However, make your own experiences, it's just some outlook not that you tell me later I didn't notice it.</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload