6

I am currently using the following method of hashing resources for lookups:

$foo = socket_create(...);
$bar = socket_create(...);

$map[(int)$foo] = 'foo';
$map[(int)$bar] = 'bar';

echo $map[(int)$foo]; // "foo"

Is integer casting the best option for this? If not, what other hashing method would be better or more efficient? These lookups will be performed in a collection into the hundreds, many times per second in a tight loop (socket polling), so I've already ruled out iteration-based solutions.

Edit:

To explain my situation a little better, the socket_select() function takes arrays of socket resources by reference and modifies them such that after the function call, they will contain only the resources which have changed (e.g. ready to be read from). I use a Socket class as a wrapper for socket resources, to make my code more abstract and testable:

$socketObject = new Socket($socketResource);

Another of my classes keeps a list of all socket resources that need to be polled every time we call socket_select():

$reads = [$socketResource1, $socketResource2, ...];
socket_select($reads, null, null, 0);

After the call to socket_select(), I know which socket resources have changed, but to do anything meaningful in my code, I need to know which socket objects those resources correspond to. Thus, I need some way to map socket resources to their objects:

foreach ($reads as $socketResource) {
    // Which socket object does $socketResource correspond to here?
    // Currently, I use a solution like this:
    $socketObject = $this->map[(int)$socketResource];
    // Unfortunately, this behavior isn't guaranteed, so it isn't reliable...
}

4 Answers 4

8
+100

The observed behavior when casting resources to integer is undefined (see Caution note on the bottom of the page). So even if this works now and reliably did for a long time, you have to be aware that it's nothing you can rely on not to change without notice.

Edit after clarifications:

Instead of putting the resource as key, use two arrays. One mapping the hashes of your Socket objects to the actual objects. And another mapping the same hashes to the resource. Then pass the latter array to socket_select. Under the premise that the function will not change the array keys, you can then iterate the array and use the key to look up the Socket in O(1):

$r1 = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$r2 = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

$s1 = new Socket($foo);
$s2 = new Socket($bar);

$socketMap = array(
    spl_object_hash($s1) => $s1,
    spl_object_hash($s2) => $s2
);

$reads = array(
    spl_object_hash($s1) => $r1,
    spl_object_hash($s2) => $r2
);

socket_select($reads, null, null, 0);

foreach (array_keys($reads) as $hash) {
    $socketObject = $socketMap[$hash];
}

Update: Casting resource to integer is no longer undefined, as seen in the linked manual page. If a resource is converted to an integer, then the result will be the unique resource number assigned to the resource by PHP at runtime.

Sign up to request clarification or add additional context in comments.

3 Comments

I actually never thought about passing an associative array to socket_select(); it might just work. I'll have to test this out later. I'll follow up and let you know. Thanks!
I just tested this, and the keys are preserved through the socket_select() call, and everything works great. I've accepted your answer and started a bounty (24 hrs before I can award it) because I really appreciate your effort and help. Thank you!
Note: Casting resource to integer is no longer undefined, as seen in the linked manual page. If a resource is converted to an integer, then the result will be the unique resource number assigned to the resource by PHP at runtime. source
2

I use this function with multi_curl, very effective for sorting to make sure text doesn't line up randomly:

function get_resource_id($resource) {
    if (!is_resource($resource))
        return false;

    return array_pop(explode('#', (string)$resource));
}

Comments

1

I know the question is old, but as of today you can also use linked-hash-map and use a resource as an array key.

You can install it with Composer:

composer require tonix-tuft/linked-hash-map

And use it like this:

<?php

// ...

use LinkedHashMap\LinkedHashMap;

$map = new LinkedHashMap();

$foo = socket_create(...);
$bar = socket_create(...);

$map[$foo] = 'foo';
$map[$bar] = 'bar';

echo $map[$foo]; // "foo"

2 Comments

Shameless self-promotion... I like it tho👌🏻 (the package as well).
Glad you like it! What did you use it for? Just asking out of curiosity :D
0

I propose you make a collection object instead of $map variable, eg.

class ResourceCollection implements ArrayAccess {

  private $map = array();

  /**
   * returns index of element or FALSE if not existent
   */
  protected function getIndex($offset){
    if(is_resource($offset)){
      $index = array_search($offset, $this->map);
    }
    else // you can add more tests if you need
      if(isset($this->map[$offset]))
        $index = $offset;
      else
        $index = false;
    return $index;
  }

  /**
   * required by ArrayAccess interface
   */
  public function offsetExists($offset){
    return ($this->getIndex($offset) === false)? false : true;
  }



  /**
   * required by ArrayAccess interface
   */
  public function offsetGet($offset){
    $index = $this->getIndex($offset);
    if($index === false)
      throw new ... // or handle error of non-existent element
    return $this->map[$index];
  }

// etc., implement ArrayAccess interface, Iterator and anything you want
}

Although I've not tested it, this should allow you to access the object as if it were an array and I hope that this way (there is no documentation for this) resources could be used as array indices.

2 Comments

Thank you for your answer, but array_search() uses iteration, so this is an O(n) solution, where I was looking for an O(1) solution instead.
You can combine both solutions so you won't have to think about two variables :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.