【问题标题】:Static class initializer in PHPPHP中的静态类初始化器
【发布时间】:2011-03-19 18:35:18
【问题描述】:

我有一个带有一些静态函数的辅助类。类中的所有函数都需要一个“繁重”的初始化函数才能运行一次(就好像它是一个构造函数一样)。

是否有实现此目的的良好做法?

我唯一想到的就是调用init 函数,如果它已经运行过一次就中断它的流程(使用静态$initialized var)。问题是我需要在每个类的函数上调用它。

【问题讨论】:

  • 正在讨论的是Static Class Constructor RFC,它将提供一种替代方法。
  • 未来读者:Here are code details and a discussion of the approach user258626 said he was thinking of doing。请将其与接受的答案进行比较。决定你想要哪个。或做 other 答案之一;我建议您不要盲目采用已接受的答案。要点:作为一般原则,最好在编写类时支付一次编码费用,以使调用者更简单。
  • 我希望我们可以重构 SO,将接受的答案放入一个新问题“PHP 中的单例模式是什么样的?” (这是一个很好的答案)并使 user258626 的答案(或类似的答案)成为该问题的公认答案。

标签: php class static constructor initializer


【解决方案1】:

注意:这正是 OP 所说的。(但没有显示代码。)我在此处显示详细信息,以便您可以将其与接受的答案进行比较。我的观点是,OP 的原始直觉是,恕我直言,比他接受的答案更好。


鉴于接受的答案有多么高,我想指出对静态方法一次性初始化的“天真”答案,几乎没有比 Singleton 的实现更多的代码——并且 有一个基本优势

final class MyClass  {
    public static function someMethod1() {
        MyClass::init();
        // whatever
    }

    public static function someMethod2() {
        MyClass::init();
        // whatever
    }


    private static $didInit = false;

    private static function init() {
        if (!self::$didInit) {
            self::$didInit = true;
            // one-time init code.
        }
    }

    // private, so can't create an instance.
    private function __construct() {
        // Nothing to do - there are no instances.
    }
}

这种方法的优点,是您可以使用简单的静态函数语法进行调用:

MyClass::someMethod1();

将其与接受的答案所需的呼叫进行对比:

MyClass::getInstance->someMethod1();

作为一般原则,最好在编写类时支付一次编码费用,以使调用者更简单。


如果您使用 PHP 7.4 的 opcode.cache,则使用 Victor Nicollet's answer。简单的。无需额外编码。没有“高级”编码可以理解。 (我建议包括 FrancescoMM 的评论,以确保“init”永远不会执行两次。)请参阅 Szczepan's explanation,了解为什么 Victor 的技术不适用于 opcode.cache

如果您 ARE 使用opcode.cache,那么我的答案就如您所愿。成本只是在每个公共方法的开头添加MyClass::init(); 行。注意:如果您想要公共属性,请将它们编码为 get / set 方法对,以便您可以添加 init 调用。

(Private 成员不需要 init 调用,因为它们无法从外部访问 - 所以一些 public 方法已经被调用,当时执行到达私有成员。)

【讨论】:

  • 我更喜欢这个,因为这个类是独立的。我只将测试放在可能是第一个调用的方法中。请注意,测试属性可以是需要使用代码初始化的属性,通过将其设为 false,然后方法可以在其为 false 时调用 init(),否则使用该属性的值。
  • @Patanjali - 我建议使用专用属性static $didInit,而不是依赖于特定于类的某些属性。这使代码更加明显(如果您在多个类中使用相同的技术,则更加一致)。额外的内存成本可以忽略不计,因为它是每个类的单个静态属性(如果您确实创建了类的实例,它不会使实例变大,它就在类本身上)。
  • (@)Toolmaker史蒂夫。如果您想要一个通用机制,我同意,因为最好保持一致。但是,如果不是所有属性都可以同时初始化,因为类值是通过其他过程建立的,那么在需要时执行相关属性是可以的。
  • 这种方法会使事情难以维护。考虑一下:如果您使用 init 方法设置某些属性…… 1) 您将不得不继续检查它是否在每个方法中都进行了 init (WET); 2)如果你想访问一个属性,你必须跟踪你是否已经调用了一个初始化该属性的方法。在我看来,使用类的实例和构造函数的好处要好得多(换句话说,OP 最好的回答是否定的,“不要这样做。”)
  • 这种方法使MyClass 稍微湿润了一点,但它使MyClass 的每个调用者都变得更干了,因为他们不需要说MyClass::getInstance->someMethod1();。维护MyClass时,代码就在你面前,所以如果你必须在某个地方弄湿,它应该在这里。
【解决方案2】:

我将其发布为答案,因为这在 PHP 7.4 中非常重要。

PHP 7.4 的opcache.preload 机制使得为类预加载操作码成为可能。如果您使用它来预加载包含类定义一些副作用的文件,则该文件中定义的类将“存在”于此 FPM 服务器及其工作人员执行的所有后续脚本,但副作用不会生效,并且自动加载器不需要包含它们的文件,因为该类已经“存在”。这完全击败了所有依赖于在包含类定义的文件中执行顶级代码的静态初始化技术。

【讨论】:

    【解决方案3】:

    有一种方法可以调用init()方法一次并禁止它的使用,你可以将函数变成私有初始化器并在类声明后调用它,如下所示:

    class Example {
        private static function init() {
            // do whatever needed for class initialization
        }
    }
    (static function () {
        static::init();
    })->bindTo(null, Example::class)();
    

    【讨论】:

    • 这看起来非常有趣
    • 如何在课外调用private init?你能解释一下你在这里做什么的细节吗?
    • @ToolmakerSteve 正如docs所说的“静态闭包不能有任何绑定对象(参数newthis的值应该为NULL),但是这个函数仍然可以用来改变它们的类范围。” 这就是为什么闭包范围绑定到Example::class 所以可以调用私有方法。我有一个错误,导致 init() 方法应该是 static - 修复了示例。
    • 编辑了示例并添加了static 修饰符用于关闭,因为它不会绑定到任何实例。
    • 好吧,其实我们甚至不需要init()方法,即我们可以将所有初始化代码直接放入这个匿名函数中,这个匿名函数本身可以充当静态构造函数。
    【解决方案4】:

    如果您不喜欢 public 静态初始化程序,反射可能是一种解决方法。

    <?php
    
    class LanguageUtility
    {
        public static function initializeClass($class)
        {
            try
            {
                // Get a static method named 'initialize'. If not found,
                // ReflectionMethod() will throw a ReflectionException.
                $ref = new \ReflectionMethod($class, 'initialize');
    
                // The 'initialize' method is probably 'private'.
                // Make it accessible before calling 'invoke'.
                // Note that 'setAccessible' is not available
                // before PHP version 5.3.2.
                $ref->setAccessible(true);
    
                // Execute the 'initialize' method.
                $ref->invoke(null);
            }   
            catch (Exception $e)
            {
            }
        }
    }
    
    class MyClass
    {
        private static function initialize()
        {
        }
    }
    
    LanguageUtility::initializeClass('MyClass');
    
    ?>
    

    【讨论】:

    • 这与 PHP 7.4 opcache.preload 不兼容;看我的回答。
    【解决方案5】:

    注意 - 提出此建议的 RFC 仍处于草案状态。


    class Singleton
    {
        private static function __static()
        {
            //...
        }
        //...
    }
    

    建议用于 PHP 7.x(请参阅 https://wiki.php.net/rfc/static_class_constructor

    【讨论】:

    • 该 RFC 还没有退出草稿阶段。请不要链接或举例说明未经投票批准的内容。它会让新用户感到困惑,因为他们还没有意识到这还不可用
    【解决方案6】:

    听起来你最好使用单例而不是一堆静态方法

    class Singleton
    {
      /**
       * 
       * @var Singleton
       */
      private static $instance;
    
      private function __construct()
      {
        // Your "heavy" initialization stuff here
      }
    
      public static function getInstance()
      {
        if ( is_null( self::$instance ) )
        {
          self::$instance = new self();
        }
        return self::$instance;
      }
    
      public function someMethod1()
      {
        // whatever
      }
    
      public function someMethod2()
      {
        // whatever
      }
    }
    

    然后,在使用中

    // As opposed to this
    Singleton::someMethod1();
    
    // You'd do this
    Singleton::getInstance()->someMethod1();
    

    【讨论】:

    • 我想为私有构造函数和getInstance() 设置-1(但我不会)...你将很难有效地测试...至少让它受到保护以便您有选择...
    • @ircmaxell - 你只是在谈论单例模式本身的问题,真的。并且任何人在 SO 上发布的代码都不应被视为具有权威性——尤其是仅用于说明性的简单示例。每个人的场景和情况都不一样
    • 20 整行??!?!?天哪,这个答案的作者不知道代码行是一种宝贵的资源吗?!?你知道它们不是长在树上的!
    • @PeterBailey 代码行除了粘合之外什么都不做会让人分心,并降低代码的可维护性。
    • @ekevoo 我不是单例模式的作者,你知道的。不要杀死信使。
    【解决方案7】:

    实际上,我在需要初始化(或至少需要执行一些代码)的静态类上使用公共静态方法__init__()。然后,在我的自动加载器中,当它加载一个类时,它会检查is_callable($class, '__init__')。如果是,它会调用该方法。快速、简单、有效...

    【讨论】:

    • 这也是我的建议。我过去也这样做过,但称它为__initStatic()。感觉就像 PHP 需要的东西,懂 Java。
    • 对于我们这些使用作曲家的人:我发现了这个:packagist.org/packages/vladimmi/construct-static
    • @iautomation 没试过,但这值得放在自己的答案中!这是一种简单而现代的方法。
    • 对于那些在作曲家是互联网胆汁的专业制作环境中工作的人......这个答案非常有效。
    【解决方案8】:
    // file Foo.php
    class Foo
    {
      static function init() { /* ... */ }
    }
    
    Foo::init();
    

    这样,在包含类文件时会发生初始化。您可以通过使用自动加载确保仅在必要时(且仅一次)发生这种情况。

    【讨论】:

    • 我不明白你的问题。以上所有内容都发生在包含的文件中。
    • @VictorNicollet,这很难看。您的代码使 init 成为公共方法,如果它是私有的,它将无法工作。难道没有像java静态类初始化器那样更干净的方式吗?
    • @Pacerier 如果 init() 在第二次被调用时什么都不做,那么它是否公开也没关系...static function init() { if(self::$inited) return; /* ... */ }
    • @Pacerier 任何接受参数的构造函数或初始化程序的最终结果都是将超出范围的数据摄取到类中。你必须在某个地方处理它。
    • 这与 PHP 7.4 的 opcache.preload 不兼容。如果文件在预加载脚本中预加载,则该类将“存在”,但不会影响该文件中顶级代码的影响 - 并且自动加载将不需要该文件,因为该类存在,您也不需要它因为它会导致类被重新定义!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-06
    • 1970-01-01
    相关资源
    最近更新 更多