php中的基本自动加载和命名空间(Basic autoload and namespace in php)

编程入门 行业动态 更新时间:2024-10-22 23:38:43
php中的基本自动加载和命名空间(Basic autoload and namespace in php)

我正在更新我刚才写的框架。 我想采用名称空间等新标准并使用自动加载功能。

现在我的框架有一个非常基本但功能自动加载功能,看起来像这样:

protected function runClasses() { $itemHandler = opendir( V_APP_PATH ); while( ( $item = readdir( $itemHandler ) ) !== false ) { if ( substr( $item, 0, 1 ) != "." ) { if( !is_dir( $item ) && substr( $item, -10 ) == ".class.php" ) { if( !class_exists( $this->registry->$item ) ) { require_once( V_APP_PATH . $item ); $item = str_replace( ".class.php", "", $item ); $this->registry->$item = new $item( $this->registry ); } } } } }

正如你在代码中看到的,这个函数仅限于一个文件夹,但它的优点是它将类加载到我的注册表中,允许我通过执行类似于$this->registry->Class->somevar来访问其他文件中的特定类。 $this->registry->Class->somevar这是我需要的功能。

我需要/想要完成的是使用自动加载器功能但是该功能不限于单个文件夹,而是能够浏览多个文件夹并实例化所需的类。

我只有一些测试文件,这是我当前的文件结构:

对于MyClass2,我有:

namespace model; Class MyClass2 { function __construct() { echo "MyClass2 is now loaded!"; } }

对于MyClass1我有:

Class MyClass1 { function __construct() { echo "MyClass1 is now loaded!<br />"; } }

对于Autoload我有:

function __autoload( $className ) { $file = $className . ".php"; printf( "%s <br />", $file ); if(file_exists($file)) { require_once $file; } } $obj = new MyClass1(); $obj2 = new model\MyClass2();

我的问题是设置的方式它无法找到MyClass2的文件所以我想知道我做错了什么,其次,有没有像我的第一个“自动加载”功能,不需要指定命名空间在自动加载文件中并将其分配给我的注册表?

很抱歉这么长的问题,但非常感谢任何帮助。

I'm in the process of updating a framework that I wrote a while ago. I would like to adopt new standards such as namespaces and using the autoload feature.

Right now my framework has a very rudimentary but functional autoload function that looks like this:

protected function runClasses() { $itemHandler = opendir( V_APP_PATH ); while( ( $item = readdir( $itemHandler ) ) !== false ) { if ( substr( $item, 0, 1 ) != "." ) { if( !is_dir( $item ) && substr( $item, -10 ) == ".class.php" ) { if( !class_exists( $this->registry->$item ) ) { require_once( V_APP_PATH . $item ); $item = str_replace( ".class.php", "", $item ); $this->registry->$item = new $item( $this->registry ); } } } } }

As you can see in the code this function is limited to a single folder only, but the advantage to it is it loads the class into my registry allowing me to access that specific class in other files by doing something similar to $this->registry->Class->somevar which is the functionality I need.

What I'm needing/wanting to accomplish is to use the autoloader function but have that function not limited to a single folder, instead be able to navigate through multiple folders and instantiate the needed classes.

I have just some test files going and here is my current file structure:

For MyClass2 I have:

namespace model; Class MyClass2 { function __construct() { echo "MyClass2 is now loaded!"; } }

For MyClass1 I have:

Class MyClass1 { function __construct() { echo "MyClass1 is now loaded!<br />"; } }

And for Autoload I have:

function __autoload( $className ) { $file = $className . ".php"; printf( "%s <br />", $file ); if(file_exists($file)) { require_once $file; } } $obj = new MyClass1(); $obj2 = new model\MyClass2();

My Question is the way that is set up it can't find the file for MyClass2 so I'm wondering what I've done wrong and secondly, is there a way like my first "autoload" function to not need to specify the namespace in the autoload file and assign it to my registry?

Sorry for such a lengthy question but any help is greatly appreciated.

最满意答案

我在这看到两件事。

第一个使你的问题有点复杂。 您希望使用命名空间,但您当前的配置是通过文件系统。 到目前为止,类定义文件的文件名不包含命名空间。 所以你不能像实际那样继续。

第二个是你没有PHP自动加载所涵盖的内容,你只需加载一组已定义的类并在注册表中注册它。

我不确定你是否需要PHP自动加载。 当然,将两者结合在一起看起来很有希望。 解决第一点可能会帮助你解决后面的问题,所以我建议先从它开始。

让我们使隐藏的依赖项更加明显。 在您当前的设计中,您有三件事:

对象在注册表中注册的名称。 包含类定义的文件名。 类本身的名称。

2.和3.的值在一个中,您从文件名解析类本身的名称。 如上所述,命名空间现在变得复杂。 解决方案很简单,您可以从包含此信息的文件中读取,而不是从目录列表中读取。 轻量级配置文件格式是json:

{ "Service": { "file": "test.class.php", "class": "Library\\Of\\Something\\ConcreteService" } }

现在包含三个所需的依赖项,用于通过名称将类注册到注册表中,因为文件名也是已知的。

然后,您可以在注册表中注册类:

class Registry { public function registerClass($name, $class) { $this->$name = new $class($this); } }

并为json格式添加一个加载器类:

interface Register { public function register(Registry $registry); } class JsonClassmapLoader implements Register { private $file; public function __construct($file) { $this->file = $file; } public function register(Registry $registry) { $definitions = $this->loadDefinitionsFromFile(); foreach ($definitions as $name => $definition) { $class = $definition->class; $path = dirname($this->file) . '/' . $definition->file; $this->define($class, $path); $registry->registerClass($name, $class); } } protected function define($class, $path) { if (!class_exists($class)) { require($path); } } protected function loadDefinitionsFromFile() { $json = file_get_contents($this->file); return json_decode($json); } }

这里没有太大的魔力,json文件中的文件名相对于它的目录。 如果尚未定义类(此处触发PHP自动加载),则需要该类的文件。 完成后,该类按其名称注册:

$registry = new Registry(); $json = new JsonClassmapLoader('path/registry.json'); $json->register($registry); echo $registry->Service->invoke(); # Done.

这个例子也很简单,很有效。 所以第一个问题就解决了。

第二个问题是自动加载。 这个当前的变体和你以前的系统确实隐藏了其他东西。 有两个中心事情要做。 一个是实际加载类定义,另一个是实例化对象。

在您的原始示例中,技术上不需要自动加载,因为对象在注册表中注册的那一刻,它也是实例化的。 您这样做也是为它分配注册表。 我不知道你是否只是因为这样做,或者这只是发生在你身上。 你在问题中写下你需要的。

因此,如果您想将自动加载到您的注册表中(或延迟加载),这会有所不同。 由于你的设计已经搞砸了,让我们继续添加更多魔力。 您希望将注册表组​​件的实例化推迟到第一次使用它的时刻。

在注册表中,组件的名称比实际类型更重要,这已经是非常动态的,只是一个字符串。 要延迟组件创建,在注册时但在访问时不会创建类。 这可以通过使用需要新类型注册表的__get函数来实现:

class LazyRegistry extends Registry { private $defines = []; public function registerClass($name, $class) { $this->defines[$name] = $class; } public function __get($name) { $class = $this->defines[$name]; return $this->$name = new $class($this); } }

用法示例再次完全相同,但是,注册表的类型已更改:

$registry = new LazyRegistry(); $json = new JsonClassmapLoader('path/registry.json'); $json->register($registry); echo $registry->Service->invoke(); # Done.

所以现在已经推迟了具体服务对象的创建,直到首次访问。 但是,这仍然不是自动加载。 类定义的加载已经在json加载器中完成。 它不会因为已经创造出动态和魔力,而不是那样。 我们需要为每个类启动一个自动加载器,它应该在第一次访问对象时启动。 例如,我们实际上希望能够在应用程序中使用腐烂的代码,这些代码可能永远不会被注意到,因为我们不关心它是否被使用。 但是我们不想把它加载到内存中。

对于自动加载,您应该了解spl_autoload_register ,它允许您具有多个自动加载器功能。 有很多原因可以解释为什么它通常很有用(例如,想象你使用第三方软件包),但这个动态魔术盒称为你的Registry ,它只是这项工作的完美工具。 一个直接的解决方案(并没有进行任何过早的优化)是为我们在注册表定义中的每个类注册一个自动加载器函数。 这需要一种新型的加载器,自动加载器功能只需两行代码:

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->register($registry); echo $registry->Service->invoke(); # Done.

现在你可以懒得下地狱。 这意味着,要再次实际更改代码。 因为您希望远程实现将这些文件实际放入该特定目录的必要性。 等等,这就是你要求的,所以我们把它留在这里。

否则,请考虑使用将在首次访问时返回实例的callables配置注册表。 这通常会使事情变得更加灵活。 如图所示,自动加载与您实际可以离开基于目录的方法无关,您不再关心代码打包在具体的位置(http://www.getcomposer.org/)。

整个代码示例完整(没有registry.json和test.class.php ):

class Registry { public function registerClass($name, $class) { $this->$name = new $class($this); } } class LazyRegistry extends Registry { private $defines = []; public function registerClass($name, $class) { $this->defines[$name] = $class; } public function __get($name) { $class = $this->defines[$name]; return $this->$name = new $class($this); } } interface Register { public function register(Registry $registry); } class JsonClassmapLoader implements Register { private $file; public function __construct($file) { $this->file = $file; } public function register(Registry $registry) { $definitions = $this->loadDefinitionsFromFile(); foreach ($definitions as $name => $definition) { $class = $definition->class; $path = dirname($this->file) . '/' . $definition->file; $this->define($class, $path); $registry->registerClass($name, $class); } } protected function define($class, $path) { if (!class_exists($class)) { require($path); } } protected function loadDefinitionsFromFile() { $json = file_get_contents($this->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->register($registry); echo $registry->Service->invoke(); # Done.

我希望这是有帮助的,但是这主要是在沙盒中播放,你迟早会粉碎它。 您实际想要了解的是控制反转,依赖注入,然后是依赖注入容器。

你有的注册表是某种气味。 这一切都充满了魔力和动力。 您可能认为这对于开发来说很酷或者在系统中有“插件”(它很容易扩展),但是您应该将对象的数量保持在较低水平。

Magic可能很难调试,所以你可能想要检查json文件的格式,如果它在你的情况下首先是有意义的,以防止第一手配置问题。

还要考虑传递给每个构造函数的注册表对象不是一个参数,而是表示动态数量的参数。 这将开始迟早产生副作用。 如果您使用的注册表太多,那么越早越好。 这种副作用会使你的维护成本很高,因为通过设计这已经存在缺陷,所以你只能通过努力工作,对回归等进行大量集成测试来控制它。

然而,做出自己的经验,这只是一些前景而不是你以后告诉我我没有注意到它。

I see two things here.

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.

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.

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.

Let's make the hidden dependencies more visible. In your current design you have got three things:

The name under which an object is registered in the registry. The filename which contains the class definition. The name of the class itself.

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:

{ "Service": { "file": "test.class.php", "class": "Library\\Of\\Something\\ConcreteService" } }

This contains now the three needed dependencies to register a class by a name into the registry because the filename is known as well.

You then allow to register classes in the registry:

class Registry { public function registerClass($name, $class) { $this->$name = new $class($this); } }

And add a loader class for the json format:

interface Register { public function register(Registry $registry); } class JsonClassmapLoader implements Register { private $file; public function __construct($file) { $this->file = $file; } public function register(Registry $registry) { $definitions = $this->loadDefinitionsFromFile(); foreach ($definitions as $name => $definition) { $class = $definition->class; $path = dirname($this->file) . '/' . $definition->file; $this->define($class, $path); $registry->registerClass($name, $class); } } protected function define($class, $path) { if (!class_exists($class)) { require($path); } } protected function loadDefinitionsFromFile() { $json = file_get_contents($this->file); return json_decode($json); } }

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:

$registry = new Registry(); $json = new JsonClassmapLoader('path/registry.json'); $json->register($registry); echo $registry->Service->invoke(); # Done.

This example as well is pretty straight forward and it works. So the first problem is solved.

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.

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.

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.

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 __get function which requires a new type of Registry:

class LazyRegistry extends Registry { private $defines = []; public function registerClass($name, $class) { $this->defines[$name] = $class; } public function __get($name) { $class = $this->defines[$name]; return $this->$name = new $class($this); } }

The usage example again is quite the same, however, the type of the registry has changed:

$registry = new LazyRegistry(); $json = new JsonClassmapLoader('path/registry.json'); $json->register($registry); echo $registry->Service->invoke(); # Done.

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.

For autoloading you should be aware of spl_autoload_register 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 Registry 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:

class LazyJsonClassmapLoader extends JsonClassmapLoader { protected function define($class, $path) { $autoloader = function ($classname) use ($class, $path) { if ($classname === $class) { require($path); } }; spl_autoload_register($autoloader); } }

The usage example again didn't change much, just the type of the loader:

$registry = new LazyRegistry(); $json = new LazyJsonClassmapLoader('path/registry.json'); $json->register($registry); echo $registry->Service->invoke(); # Done.

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.

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/).

The whole code-example in full (without registry.json and test.class.php):

class Registry { public function registerClass($name, $class) { $this->$name = new $class($this); } } class LazyRegistry extends Registry { private $defines = []; public function registerClass($name, $class) { $this->defines[$name] = $class; } public function __get($name) { $class = $this->defines[$name]; return $this->$name = new $class($this); } } interface Register { public function register(Registry $registry); } class JsonClassmapLoader implements Register { private $file; public function __construct($file) { $this->file = $file; } public function register(Registry $registry) { $definitions = $this->loadDefinitionsFromFile(); foreach ($definitions as $name => $definition) { $class = $definition->class; $path = dirname($this->file) . '/' . $definition->file; $this->define($class, $path); $registry->registerClass($name, $class); } } protected function define($class, $path) { if (!class_exists($class)) { require($path); } } protected function loadDefinitionsFromFile() { $json = file_get_contents($this->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->register($registry); echo $registry->Service->invoke(); # Done.

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.

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.

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.

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..

However, make your own experiences, it's just some outlook not that you tell me later I didn't notice it.

更多推荐

本文发布于:2023-08-05 04:47:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1427595.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:自动加载   空间   php   namespace   autoload

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!