【问题标题】:Get Instance ID of an Object in PHP在 PHP 中获取对象的实例 ID
【发布时间】:2011-02-21 18:37:54
【问题描述】:

我不久前在 StackOverflow 上了解到 we can get the "instance ID" of any resource,例如:

var_dump(intval(curl_init()));  // int(2)
var_dump(intval(finfo_open())); // int(3)
var_dump(intval(curl_init()));  // int(4)
var_dump(intval(finfo_open())); // int(5)
var_dump(intval(curl_init()));  // int(6)

我需要类似但适用于类的东西:

class foo {
    public function __construct() {
        ob_start();
        var_dump($this); // object(foo)#INSTANCE_ID (0) { }
        echo preg_replace('~.+#(\d+).+~s', '$1', ob_get_clean());
    }
}

$foo = new foo();  // 1
$foo2 = new foo(); // 2

上述方法可行,但我希望有一个更快的解决方案,或者至少是一个不涉及输出缓冲区的解决方案。请注意,这不一定会在构造函数中甚至在类本身中使用!

spl_object_hash() 不是我要找的,因为这两个对象产生相同的哈希值

之前的问题包含spl_object_hash 输出的错误示例;确保两个对象同时存在会产生细微不同的哈希:

var_dump(spl_object_hash($foo));  // 0000000079e5f3b60000000042b31773
var_dump(spl_object_hash($foo2)); // 0000000079e5f3b50000000042b31773

转换为 int 类资源似乎不适用于对象:

注意: foo 类的对象无法转换为 int。

有没有不使用对象属性的快速方法来获取相同的输出

除了var_dump(),我通过反复试验发现debug_zval_dump()也输出对象实例,不幸的是它还需要输出缓冲,因为它不返回结果。

【问题讨论】:

标签: php class object resources instance


【解决方案1】:

看看spl_object_hash()。使用示例:

$id = spl_object_hash($object);

请注意,您需要 PHP 5 >= 5.2.0 才能工作。

【讨论】:

  • 听起来你想使用工厂模式
  • @Mark:不幸的是,我很确定情况并非如此。
  • @Alix Axel:这绝对是您概念中的一个缺陷!在构造函数中只运行一次代码是违反每个 OOP 规则的,非常详细地描述您的问题,我们可以找到解决方案,但您尝试做的只不过是一个 UGLY HACK。
  • @Tobias:我知道 - 忘记构造函数,这只是一个例子。我想知道一个对象的实例 ID,与我所在的范围无关(甚至在类之外)。
  • @Alix Axel:告诉我们真正的问题,我们可以为您提供真正的帮助。我可以怀疑是否存在真正的网络设计模式或最佳实践来解决您的问题,但只要您告诉我们您的真正问题。
【解决方案2】:

spl_object_hash() 可以在这里为您提供帮助。它

返回对象的唯一标识符

对于给定的实例来说总是相同的。

EDIT OP 评论后:

您可以使用静态类属性来实现这样的行为,例如:

class MyClass 
{
    private static $_initialized = false;

    public function __construct()
    {
        if (!self::$_initialized) {
            self::$_initialized = true;
            // your run-only-once code 
        }
    }
}

但实际上这与您最初的问题无关。

【讨论】:

  • 注意,来自spl_object_hash 文档:“当一个对象被销毁时,它的哈希值可能会被其他对象重用。”我遇到过这个问题,这使得它在某些方面毫无用处。
【解决方案3】:

我没有启用 PECL runkit 来对此进行测试,但这可能允许您在第一次创建类的实例后从类定义中删除构造函数代码。

是否可以从构造函数中删除构造函数将是一个有趣的实验。

【讨论】:

  • 我了解您正在尝试回答之前 cmets 中讨论的内容,但我只想获取对象的实例 ID 号,这是我唯一的目标。顺便说一句,很好的代表(2 ^ 11)! :)
【解决方案4】:

嗯,是的,带有扩展名。

请注意,用于同时被销毁的对象的句柄可以重复使用。

使用phpize && ./configure && make && make install 构建

testtext.h

#ifndef PHP_EXTTEST_H
# define PHP_EXTTEST_H
# ifdef HAVE_CONFIG_H
#  include<config.h>
# endif
# include <php.h>
extern zend_module_entry testext_module_entry;
#define phpext_testext_ptr &testext_module_entry
#endif

testtext.c

#include "testext.h"

PHP_FUNCTION(get_object_id)
{
    zval *obj;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &obj)
            == FAILURE) {
        return;
    }

    RETURN_LONG(Z_OBJ_HANDLE_P(obj));
}

static zend_function_entry ext_functions[] = {
    PHP_FE(get_object_id, NULL)
    {NULL, NULL, NULL, 0, 0}
};

zend_module_entry testext_module_entry = {
    STANDARD_MODULE_HEADER,
    "testext",
    ext_functions, /* Functions */
    NULL, /* MINIT */
    NULL, /* MSHUTDOWN */
    NULL, /* RINIT */
    NULL, /* RSHUTDOWN */
    NULL, /* MINFO */
    NO_VERSION_YET,
    STANDARD_MODULE_PROPERTIES
};

ZEND_GET_MODULE(testext)

config.m4

PHP_ARG_ENABLE(testext,
  [Whether to enable the "testext" extension],
  [  enable-testext         Enable "testext" extension support])

if test $PHP_EXTTEST != "no"; then
  PHP_SUBST(EXTTEST_SHARED_LIBADD)
  PHP_NEW_EXTENSION(testext, testext.c, $ext_shared)
fi

测试脚本

<?php
$a = new stdclass();
$b = new stdclass();
var_dump(get_object_id($a));
var_dump(get_object_id($b));

输出

整数(1) 整数(2)

【讨论】:

  • 虽然这不是我希望的解决方案,但我会支持它,如果没有更好的解决方案,我会奖励这个答案。你不必费尽心思,但干得好! :)
  • 请注意,这里返回的“对象句柄”是现有spl_object_hash函数使用的两条信息之一,可以在其源代码中看到:lxr.php.net/xref/PHP_5_6/ext/spl/php_spl.c#785
【解决方案5】:

如果您不想使用输出缓冲...也许使用var_export 而不是var_dump

【讨论】:

  • var_export()var_dump() 那样包含实例 ID。
【解决方案6】:

只要你实现了你需要的所有类的基类,你就可以这样做:

class MyBase
{
    protected static $instances = 0;
    private $_instanceId  = null;
    public function getInstanceId()
    {
        return $this->_instanceId;
    }

    public function __construct()
    {
        $this->_instanceId = ++self::$instances;
    }
}

class MyTest extends MyBase
{
    public function Foo()
    {
        /* do something really nifty */
    }
}

$a = new MyBase();
$b = new MyBase();

$c = new MyTest();
$d = new MyTest();


printf("%d (should be 1) \n", $a->getInstanceId());
printf("%d (should be 2) \n", $b->getInstanceId());
printf("%d (should be 3) \n", $c->getInstanceId());
printf("%d (should be 4) \n", $d->getInstanceId());

输出将是:

1(应该是 1) 2(应该是 2) 3(应该是 3) 4(应该是 4)

【讨论】:

    【解决方案7】:

    你想要做的,实际上是Aspect-Oriented Programming (AOP)。

    目前至少有几个框架可用于 PHP 中的 AOP:

    • seasar(以前称为 PHPaspect)是一个与 Eclipse 集成的更大框架 - 屏幕截图显示了一个小代码 sn-p 可以回答您的问题,在整个项目中围绕特定的新语句编织一些代码。
    • php-aop 是 AOP 的轻量级框架。
    • typo3 内置了 AOP 框架。

    这对于您的需求来说可能有点过头了,但是您可能会发现探索这些想法背后的思维方式会让您陷入困境,并教您总体上思考软件开发的新方法 - AOP 是一个强大的概念,允许您根据策略和关注点或“方面”进行编程。

    PHP 等语言旨在解决编程 任务 - APO 的概念旨在解决程序员的 任务。通常,当您需要考虑如何确保每次在您的代码库中都满足特定问题时,您可以将其简单地视为您编程方式的“方面”,直接以这些术语实现它,然后计算每次都要落实您的顾虑。

    它需要较少的纪律,您可以专注于解决实际的编程任务,而不是试图通过高级结构化代码要求来构建自己的方式。

    【讨论】:

    • 谢谢,我一定会尽快看看。但是找不到任何 seasar 的屏幕截图。
    • 是的,我不认为有,抱歉 - 我的意思是向您指出这个工具:code.google.com/p/apdt 这个工具也与 Eclipse 集成,那一定是我的截图在我的脑海里。
    【解决方案8】:

    Alix,您在问题中的解决方案正是我所需要的,但实际上在对象中有对象时中断,返回 var_dump 中的最后一个 #。我修复了这个问题,使正则表达式更快,并将其放入一个不错的小函数中。

    /**
     * Get global object ID
     * From: http://stackoverflow.com/questions/2872366/get-instance-id-of-an-object-in-php
     * By: Alix Axel, non-greedy fix by Nate Ferrero
     */
    function get_object_id(&$obj) {
        if(!is_object($obj))
            return false;
        ob_start();
        var_dump($obj);// object(foo)#INSTANCE_ID (0) { }
        preg_match('~^.+?#(\d+)~s', ob_get_clean(), $oid);
        return $oid[1]; 
    }
    

    【讨论】:

      【解决方案9】:

      这有点晚了,但我没有看到这个答案,最近刚刚为调试类实现了类似的东西(处理循环引用)。你们可能知道也可能不知道var_export 等正常的打印功能,对循环引用的支持有限或没有。

      如前所述,spl_object_hash 每个实例都是唯一的,我遇到的问题是它很难看。不太适合为我的调试器打印,因为它类似于 000000006ac56bae0000000044fda36f,很难与 000000006ac56bae0000000044fda35f 进行比较。因此,就像 OP 所说的那样,我想要的只是一些实例(我只需要在每个类的基础上使用它)。

      因此对我来说简单的解决方案是执行以下操作。

          $class = get_class( $input );
          $hash = spl_object_hash( $input );
          if( !isset( $objInstances[ $class ] )){
              $objInstances[ $class ] = array();
          }
      
          $output = 'object(%s) #%s (%s){%s}'; //class, instance, prop_count, props
          if( false === ( $index = array_search($hash, $objInstances[ $class ] ) ) ){
              $index = count($objInstances[ $class ]); //set init index for instance
              $objInstances[ $class ][] = $hash;
              // .... debugging code
              $output = 'debugging result.', //sprintf 
          }else{
              $output = sprintf( $output, $class, $index, 0, '#_CIRCULAR_REFRENCE_#');
          }
      

      显然调试代码要复杂得多,但这里最重要的是通过跟踪 $objInstances 中的类和 spl 哈希,我可以轻松地在类之外分配我自己的实例编号。这意味着我不需要一些丑陋的 hack(这会影响类的代码)来获得参考号。此外,我不需要显示“丑陋”的 spl 哈希。无论如何,我为此输出的完整代码是这样的。

      $obj = new TestObj();
      $obj1 = new TestObj();
      
      $obj->setProProp($obj1);
      $obj1->setProProp($obj); //create a circular reference 
      
      object(TestObj) #0 (7){
          ["SOME_CONST":const] => string(10) 'some_const',
          ["SOMEOTHER_CONST":const] => string(16) 'some_other_const',
          ["SOME_STATIC":public static] => string(6) 'static',
          ["_PRO_STATIC":protected static] => string(10) 'pro_static',
          ["someProp":public] => string(8) 'someProp',
          ["_pro_prop":protected] => object(TestObj) #1 (7){
              ["SOME_CONST":const] => string(10) 'some_const',
              ["SOMEOTHER_CONST":const] => string(16) 'some_other_const',
              ["SOME_STATIC":public static] => string(6) 'static',
              ["_PRO_STATIC":protected static] => string(10) 'pro_static',
              ["someProp":public] => string(8) 'someProp',
              ["_pro_prop":protected] => object(TestObj) #0 (0){#_CIRCULAR_REFRENCE_#},
              ["_proProp":protected] => string(7) 'proProp'
          },
          ["_proProp":protected] => string(7) 'proProp'
      }
      

      如您所见,很容易看出object(TestObj) #0 (0){#_CIRCULAR_REFRENCE_#} 的来源。我想让这个调试代码尽可能接近原生的var_dump 输出这个。

      object(TestObj)#7 (3) {
        ["someProp"]=> string(8) "someProp"
        ["_pro_prop":protected]=> object(TestObj)#10 (3) {
          ["someProp"]=> string(8) "someProp"
          ["_pro_prop":protected]=> *RECURSION*
          ["_proProp":protected]=> string(7) "proProp"
        }
        ["_proProp":protected]=> string(7) "proProp"
      }
      

      这里的区别是我需要作为字符串返回,而不是输出到浏览器。我还希望能够显示类常量、静态属性和私有属性(使用标志来更改调试器输出的内容和深度限制)。而且,我想要更多关于循环引用是什么的信息,而不仅仅是*RECURSION*,它什么都没有告诉我。

      希望它对未来的人有所帮助。

      这是我的 Debug 类的完整代码,你可以在第 300 行找到它使用的代码

      https://github.com/ArtisticPhoenix/Evo/blob/master/Evo/Debug.php

      【讨论】:

        【解决方案10】:

        从 PHP 7.2 开始,您可以使用 spl_object_id

        $id = spl_object_id($object);
        $storage[$id] = $object;
        

        【讨论】:

          猜你喜欢
          • 2011-01-10
          • 1970-01-01
          • 2015-08-19
          • 2020-11-12
          • 2020-11-12
          • 2013-03-29
          • 2011-01-18
          • 1970-01-01
          • 2010-12-23
          相关资源
          最近更新 更多