简体   繁体   English

php中的基本自动加载和命名空间

[英]Basic autoload and namespace in php

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

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: 对于MyClass2,我有:

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

For MyClass1 I have: 对于MyClass1,我有:

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

And for Autoload I have: 对于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(); 

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

Sorry for such a lengthy question but any help is greatly appreciated. 很抱歉这么长的问题,但非常感谢任何帮助。

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. 第二个是你没有PHP自动加载所涵盖的内容,你只需加载一组已定义的类并将其注册到注册表中。

I'm not really sure if you need PHP autoloading here. 我不确定你是否需要PHP自动加载。 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: 在您目前的设计中,您有三件事:

  1. The name under which an object is registered in the registry. 对象在注册表中注册的名称。
  2. The filename which contains the class definition. 包含类定义的文件名。
  3. 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. 2.和3.的值在一个中,您从文件名解析类本身的名称。 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: 轻量级配置文件格式是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: 并为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);
    }
}

There is not much magic here, file-names in the json file are relative to the directory of it. 这里没有太大的魔力,json文件中的文件名相对于它的目录。 If a class is not yet defined (here with triggering PHP autoloading), the file of the class is being required. 如果尚未定义类(此处触发PHP自动加载),则需要该类的文件。 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: 这可以通过使用需要新类型注册表的__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);
    }
}

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. 类定义的加载已经在json加载器中完成。 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. 我们需要为每个类启动一个自动加载器,它应该在第一次访问对象时启动。 Eg 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. 对于自动加载,您应该了解spl_autoload_register ,它允许您拥有多个自动加载器功能。 There are many reasons why this is generally useful (eg 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. 有很多原因可以解释为什么它通常很有用(例如,想象你使用第三方软件包),但是这个动态魔术盒叫做你的Registry ,它只是这项工作的完美工具。 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. 否则,请考虑使用将在首次访问时返回实例的callables配置注册表。 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/). 自动加载 - 如图所示 - 与您实际可以离开基于目录的方法无关,您不再关心代码打包在具体的位置(http://www.getcomposer.org/)。

The whole code-example in full (without registry.json and test.class.php ): 整个代码示例完整(没有registry.jsontest.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. Magic可能很难调试,所以你可能想要检查json文件的格式,如果它在你的情况下首先是有意义的,以防止第一手配置问题。

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. 然而,制作你自己的经历,这只是一些前景而不是你以后告诉我我没有注意到它。

For your second question: the use of __autoload is discouraged and should be replaced with a spl_autoload_register. 对于第二个问题:不鼓励使用__autoload,应该用spl_autoload_register替换。 What the autoloader should do is split namespace and class: 自动加载器应该做的是拆分命名空间和类:

function __autoload( $classname )
{    
  if( class_exists( $classname, false ))
    return true;

  $classparts = explode( '\\', $classname );
  $classfile = '/' . strtolower( array_pop( $classparts )) . '.php';
  $namespace = implode( '\\', $classparts );

  // at this point you have to decide how to process further

}

Depending on you file structure I would suggest building a absolute path based on the namespace and classname: 根据您的文件结构,我建议根据命名空间和类名构建一个绝对路径:

define('ROOT_PATH', __DIR__);

function __autoload( $classname )
{    
  if( class_exists( $classname, false ))
    return true;

  $classparts = explode( '\\', $classname );
  $classfile = '/' . strtolower( array_pop( $classparts )) . '.php';
  $namespace = implode( '\\', $classparts );

  $filename = ROOT_PATH . '/' . $namespace . $classfile;
  if( is_readble($filename))
    include_once $filename;
}

I've took the PSR0 approach, where the namespace is part of the path. 我采用了PSR0方法,其中命名空间是路径的一部分。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM