php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Request #52657 Object id function where its uniqueness persists through entire script execution
Submitted: 2010-08-20 17:34 UTC Modified: 2020-06-10 10:40 UTC
Votes:53
Avg. Score:4.5 ± 0.8
Reproduced:48 of 48 (100.0%)
Same Version:31 (64.6%)
Same OS:30 (62.5%)
From: marco dot weber at uni-trier dot de Assigned:
Status: Wont fix Package: SPL related
PHP Version: PHP >= 5.2.0 OS: ANY
Private report: No CVE-ID: None
 [2010-08-20 17:34 UTC] marco dot weber at uni-trier dot de
Description:
------------
the problem with spl_object_hash is, that it is only unqiue for the existing objects.

For a given Object:
class container {
protected $storage=array();
  public function add(test1 $obj) {
    if(!isset($this->storage[spl_object_hash($obj)])) {
      $this->storage[spl_object_hash($obj)]=$obj;
    }
  }
}

This leads to a problem, that the add method receives the same hash in to following cases:
CASE1:
$o=new container();
$o->add(new test1("lalala"));
$o->add(new test1("lololo")); // same hash, that's NOT ok!

CASE2:
$t=new test("lalala");
$o=new container();
$o->add($t);
$o->add($t); // same hash, that's ok!

Since there is nothing wrong, with the spl_object_hash() method, i suggest to introduce a new spl_object_id() function. This could simply return an (internal) uint32, that is attached to every object on its creation. This counter gets incremented on every object that gets created. :)


Test script:
---------------
// just with spl_object_hash :/
class container {
protected $storage=array();
  public function add(test1 $obj) {
    if(!isset($this->storage[spl_object_hash($obj)])) {
      $this->storage[spl_object_hash($obj)]=$obj;
    }
  }
}

$o=new container();
$o->add(new test1("lalala")); // will be added
$o->add(new test1("lololo")); // not added - NOT as expected

$t=new test("lalala");
$o=new container();
$o->add($t); // will be added
$o->add($t); // not added - as expected

Expected result:
----------------
// with the new spl_object_id function :)
class container {
protected $storage=array();
  public function add(test1 $obj) {
    if(!isset($this->storage[spl_object_id($obj)])) {
      $this->storage[spl_object_id($obj)]=$obj;
    }
  }
}

$o=new container();
$o->add(new test1("lalala")); // will be added
$o->add(new test1("lololo")); // will be added

$t=new test("lalala");
$o=new container();
$o->add($t); // will be added
$o->add($t); // not added


Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2010-08-20 17:38 UTC] marco dot weber at uni-trier dot de
i've forgotten to write down the test1 class:
class test1 {
  public function __construct($s) { }
}
 [2010-08-21 07:59 UTC] giorgio dot liscio at email dot it
i've experienced problems with uniqueness ofspl_object_hash
i think should be rebuilt with a better one algorithm 
there is no need (i think) of another function, just fix spl_object_hash

i think it is really important to fix
 [2011-06-13 04:18 UTC] rasmus at mindplay dot dk
I agree, this is a vital feature.

Also, the description of spl_object_hash() in the documentation is highly 
misleading:

"This function returns a unique identifier for the object. This id can be used 
as a hash key for storing objects or for identifying an object."

It does NOT always return a unique identifier, not even within a single running 
script. And it CANNOT be used as a key for storing objects, neither can it be 
used to identify an object. In fact, in the footnote, it basically says so:

"When an object is destroyed, its hash may be reused for other objects."

That fact precludes the uses mentioned in the first part of the documentation.

I don't know what the intended use of that function is. It seems like it was an 
attempt to provide the functionality described in the first part of the 
documentation, because those are definitely things that could be put to good use 
in object-relational mappers, test-frameworks, etc...
 [2011-06-13 10:44 UTC] marco dot weber at uni-trier dot de
i know, that there is nothing wrong with that method, as it does exactly, what the documentation says. Nevertheless, it would be great to have another function like spl_object_id(), that generates unique ids...

Quotation from  [2010-08-20 15:34 UTC] marco dot weber at uni-trier dot de :
Since there is nothing wrong, with the spl_object_hash() method, i suggest to introduce a new spl_object_id() function. This could simply return an (internal) uint32, that is attached to every object on its creation. This counter gets incremented on every object that gets created. :)
 [2011-06-13 14:28 UTC] rasmus at mindplay dot dk
I don't think attaching a serial number to every object from the get-go is a good 
approach, since this will add overhead (memory and CPU) for every object 
constructed. Objects are relatively lightweight in PHP, and sacrificing that for a 
feature that is probably less commonly used, to me, is unacceptable.

What I would propose, is to assign a serial number the first time you access an 
object - something along the lines of this:

  public function object_serial($object)
  {
    static $next_sn = 1;

    if (!isset($object->__sn__))
      $object->__sn__ = $next_sn++;

    return $object->__sn__;
  }

You don't need to keep a serial-number in-memory until it's actually needed, and 
at that point, we'll just check and see if it already has an assigned serial-
number.

This is much simpler and easier on system-resources - the serial number is much 
lighter than the 32-character hash, and will work just as well. And since you're 
most likely going to use this value as index in an array, hash indexes will take 
up less memory, and lookups will probably be cheaper too.

Unfortunately the PHP version of this collides with the magic __set() method, 
which is why the function shown above won't always work.

If there were a way to go around the __get() and __set() methods, and directly 
access the properties of an object without colliding with these magic methods, 
that would probably be an even better solution. I would consider such a feature as 
belonging to the reflection domain - something like 
ReflectionObject::getValue($object, $name) and ReflectionObject::setValue($object, 
$name, $value) would do the trick.

(this would probably have other uses too, so perhaps that's an even better 
solution to this problem, seeing as how implementing your own object_serial() 
method is literally only a few lines of code...)
 [2012-11-15 14:55 UTC] maciej dot sz at gmail dot com
@rasmus at mindplay dot dk: the thing is PHP already creates internal unique index for each instantiated object. The requested spl_object_id() function would only have to return it. You may have seen the value of this variable while debugging your scripts. Have you not noticed the overhead? ;) Just kidding.

Anyway, your idea for workaround seemed reasonable to me, and now thanks to the traits in 5.4 I'm able to apply this unique object id to every class that I need. Just use the below trait. It is also immune to the __get/__set issue. The downside of this is that the id is a string containing class name. But if I'd need an id that is unique only within a specific class scope I can use integer value without the class name:

<?php

/**
 * Provides unique object's identifier.
 */
trait TObjectUniqueId
{
    /**
     * Object's unique id.
     *
     * @var int
     */
    protected $__oid__ = null;

    /**
     * @return string
     */
    public function getObjectUniqueId()
    {
        static $__object_index = 1;
        if ( null === $this->__oid__ ) {
            $this->__oid__ = __CLASS__ . '\\' . $__object_index++;
        }
        return $this->__oid__;
    }
}
 [2013-02-14 12:42 UTC] maciej dot sz at gmail dot com
An implementation of this is avaliable, and can be compiled as an extension: http://stackoverflow.com/a/3089587/1697320
 [2013-09-19 22:29 UTC] metamarkers at gmail dot com
Could be made into a magic property ->__id

The id could just be a signed 64-bit counter that ticks up every time an object 
is created. What would happen when you create more than 9,223,372,036,854,775,807 
objects? Refactor, probably.
 [2013-09-19 22:41 UTC] metamarkers at gmail dot com
Here's a chopped down version of my current solution.

function id ( $object ) {
    static $id = 0;
    if (!isset($object->__id) || $object->__id[0] !== spl_object_hash($object)) 
{
        $object->__id = [spl_object_hash($object),++$id];
    }
    return $object->__id[1];
}

It works for clones to a degree. Clones can be made through this function to 
automatically refresh the id.

function dupe ( $object ) {
    $clone = clone $object;
    id($clone);
    return $clone;
}
 [2014-10-08 12:47 UTC] adrien dot crivelli at gmail dot com
The workaround suggested by "maciej dot sz at gmail dot com" does not work, as it has the exact same pitfall as the existing spl_object_hash() function.

I am in favor of modifying existing spl_object_hash(). Even if it is to return an int, instead of string. I am pretty sure anybody using the function would actually be happier if we could guarantee uniqueness for the entire PHP process lifetime.
 [2015-04-19 15:39 UTC] rmf@php.net
-Summary: create a spl_object_id function +Summary: Implement function to get a truly unique object id -PHP Version: Irrelevant +PHP Version: PHP >= 5.2.0 -Assigned To: +Assigned To: rmf
 [2015-04-19 15:55 UTC] rmf@php.net
-Summary: Implement function to get a truly unique object id +Summary: Object id function where its uniqueness persists through entire script execution
 [2015-04-19 15:55 UTC] rmf@php.net
As noted in the original title, a name of spl_object_id was suggested. I would like to propose something more suggestive of the intent: spl_object_uuid.

While I went ahead and assigned this to myself because I would like to find a solution for my own needs, this is not high priority, and I am not a regular PHP contributor, so the following two things must fall into line for this to become a reality:

1. I can manage the time to investigate and implement an elegant and proper solution.
2. My patch is accepted.

I can absolutely guarantee this will not be in PHP 7. Based on my current schedule it'll likely be a few minor releases after.

If anyone else have more ample time and wants to tackle this, feel free to snatch it away and assign it to yourself ;-).
 [2017-10-24 06:17 UTC] kalle@php.net
-Status: Assigned +Status: Open -Assigned To: rmf +Assigned To:
 [2018-10-05 17:56 UTC] nikic@php.net
There are many people here saying that such a function is necessary, but the use-case is not entirely clear to me. The usage shown by OP is actually perfectly fine and exactly how spl_object_hash() and spl_object_id() are supposed to be used: If you use the hash, you should also store the object. That ensures that the hash will not reused while the hash is in use.

My suspicion is that in most cases either a) you would actually be fine with spl_object_hash() but you may not realize it, or b) what you are really looking for are weak references or weak maps, rather than object hashes.
 [2020-06-10 10:40 UTC] nikic@php.net
-Status: Open +Status: Wont fix
 [2020-06-10 10:40 UTC] nikic@php.net
Marking this won't fix per my previous comment. If you use the object ID, you need to store the object as well.

You may also want to consider using WeakReference (PHP 7.4) or WeakMap (PHP 8.0), for the case where you are not interested in forcing the object to stay alive, but need to be notified if the object ID has been released for re-use.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri Nov 22 14:01:30 2024 UTC