Получить идентификатор экземпляра объекта в PHP - PullRequest
26 голосов
/ 20 мая 2010

Я недавно узнал о StackOverflow, что мы можем получить «идентификатор экземпляра» любого ресурса , например:

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() также выводит экземпляр объекта, к сожалению, он также нуждается в буферизации вывода, поскольку не возвращает результат.

Ответы [ 9 ]

32 голосов
/ 20 мая 2010

spl_object_hash() может помочь вам здесь. Это

возвращает уникальный идентификатор для объекта

, что всегда одинаково для данного экземпляра.

РЕДАКТИРОВАТЬ после комментария OP:

Вы можете реализовать такое поведение, используя свойство статического класса, например:

class MyClass 
{
    private static $_initialized = false;

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

Но на самом деле это не имеет ничего общего с вашим первоначальным вопросом.

19 голосов
/ 22 июня 2010

Ну да, с расширением.

Обратите внимание, что маркеры, используемые для объектов, которые тем временем были уничтожены, можно использовать повторно.

Сборка с phpize && ./configure && make && make install

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

testext.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));

выход

int(1)
int(2)
3 голосов
/ 22 октября 2011

Аликс, ваше решение в вопросе было именно тем, что мне было нужно, но на самом деле ломается, когда в объекте есть объект, возвращает последний # в var_dump. Я исправил это, сделал регулярное выражение быстрее и поместил его в симпатичную маленькую функцию.

/**
 * Get global object ID
 * From: /2059298/poluchit-identifikator-ekzemplyara-obekta-v-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]; 
}
3 голосов
/ 20 мая 2010

Посмотрите на spl_object_hash(). Пример использования:

$id = spl_object_hash($object);

Обратите внимание, что для работы вам потребуется PHP 5> = 5.2.0.

1 голос
/ 21 сентября 2014

Это немного поздно для вечеринки, но я не видел этого ответа и только недавно реализовал нечто подобное для класса отладки (для обработки циклических ссылок). Как вы, ребята, можете знать или не знать обычные функции печати, такие как var_export, с ограниченной или отсутствующей циклической справочной поддержкой.

Как уже отмечалось, spl_object_hash уникален для каждого случая, проблема, с которой я столкнулся, заключается в том, что он ужасен. Не совсем подходит для печати для моего отладчика, так как это что-то вроде 000000006ac56bae0000000044fda36f, что может быть трудно сравнить с этим 000000006ac56bae0000000044fda35f. Так как, как указывалось в ОП, мне нужен был только номер экземпляра (мне это действительно нужно было только для каждого класса).

Поэтому для меня самым простым решением было сделать следующее.

    $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_#');
    }

Очевидно, что код отладки намного сложнее, но здесь важно то, что, отслеживая класс и хэш spl в $objInstances, я могу легко назначить свои собственные номера экземпляров вне класса. Это означает, что мне не нужен некрасивый хак (который влияет на код класса), чтобы получить ссылочный номер. Кроме того, мне не нужно отображать "некрасивый" хэш 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*, которая мне ничего не говорит.

Надеюсь, это поможет кому-то в будущем.

Вот полный код моего класса отладки, вы можете найти его в строке # 300

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

1 голос
/ 04 августа 2010

То, что вы пытаетесь сделать, на самом деле Аспектно-ориентированное программирование (AOP).

На данный момент для PHP в AOP доступно по крайней мере несколько платформ:

  • seasar (ранее PHPaspect) - это более крупная интегрированная среда, интегрируемая с Eclipse - на снимке экрана показан небольшой фрагмент кода, который отвечает на ваш вопрос, и сплетает некоторый код вокруг конкретного нового оператора в проекте.
  • php-aop - это облегченный фреймворк для AOP.
  • typo3 имеет встроенную платформу AOP.

Это может быть излишним для ваших нужд, но вы можете обнаружить, что изучение такого рода идей, лежащих в основе этих идей, приведет вас в тупик и научит вас новым способам думать о разработке программного обеспечения в целом - AOP является мощной концепцией , позволяя вам программировать с точки зрения стратегий и проблем, или «аспектов».

Языки, такие как PHP, были разработаны для решения программирования задач - концепция APO была разработана для решения задач программиста . Когда обычно вам нужно подумать о том, как обеспечить выполнение определенной задачи каждый раз в вашей кодовой базе, вы можете рассматривать это как просто «аспект» того, как вы программируете, реализовывать его в этих терминах напрямую и считать о ваших заботах, которые будут реализованы каждый раз.

Это требует меньше дисциплины, и вы можете сосредоточиться на решении практических задач программирования, а не пытаться проложить свой путь через высокоуровневые требования структурного кода.

В любом случае это может стоить 5 минут вашего времени; -)

Удачи!

1 голос
/ 22 июня 2010

Пока вы реализуете базовый класс для всех классов, которые вам понадобятся, вы можете делать что-то вроде этого:

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 (should be 1) 
2 (should be 2) 
3 (should be 3) 
4 (should be 4) 
0 голосов
/ 22 июня 2010

Если вы не хотите использовать буферизацию вывода ... возможно, используйте var_export вместо var_dump?

0 голосов
/ 17 июня 2010

У меня не включен runkit PECL для проверки этого, но это может позволить вам удалить код конструктора из определения класса после первого создания экземпляра класса.

Интересно, удастся ли удалить конструктор изнутри конструктора.

...