2012-12-21 20 views
6

這是必然要求設計問題,但我想在PHP中序列化或哈希封閉,這樣我有一個唯一的標識符爲該封閉。在PHP中序列化或哈希封閉

我不需要能夠從中調用閉包,我只需要一個唯一的標識符,它可以從閉包本身的內部和外部訪問,也就是說,接受閉包的方法需要生成對於關閉的ID,並關閉本身就需要能夠產生相同的ID

事情我試過到目前爲止:

$someClass = new SomeClass(); 

$closure1 = $someClass->closure(); 

print $closure1(); 
// Outputs: I am a closure: {closure} 

print $someClass->closure(); 
// Outputs: Catchable fatal error: Object of class Closure could not be converted to string 

print serialize($closure1); 
// Outputs: Fatal error: Uncaught exception 'Exception' with message 'Serialization of 'Closure' is not allowed' 

class SomeClass 
{ 
    function closure() 
    { 
     return function() { return 'I am a closure: ' . __FUNCTION__; }; 
    } 
} 

反射API似乎並沒有提供任何我也許可以用來創建一個ID。

+0

好吧,考慮你的設計質疑;)「閉合」是一個匿名函數在這裏? – dualed

+0

是一個閉包是一個匿名函數......我仍然想要一個方法來做一個散列雖然;) – Toby

+0

其中範圍是這個散列是唯一的? – dualed

回答

3

我的解決方案更通用,並考慮靜態封閉的參數。爲了使絕招,你可以傳遞一個參考瓶蓋內關閉:

class ClosureHash 
{ 
    /** 
    * List of hashes 
    * 
    * @var SplObjectStorage 
    */ 
    protected static $hashes = null; 

    /** 
    * Returns a hash for closure 
    * 
    * @param callable $closure 
    * 
    * @return string 
    */ 
    public static function from(Closure $closure) 
    { 
     if (!self::$hashes) { 
      self::$hashes = new SplObjectStorage(); 
     } 

     if (!isset(self::$hashes[$closure])) { 
      $ref = new ReflectionFunction($closure); 
      $file = new SplFileObject($ref->getFileName()); 
      $file->seek($ref->getStartLine()-1); 
      $content = ''; 
      while ($file->key() < $ref->getEndLine()) { 
       $content .= $file->current(); 
       $file->next(); 
      } 
      self::$hashes[$closure] = md5(json_encode(array(
       $content, 
       $ref->getStaticVariables() 
      ))); 
     } 
     return self::$hashes[$closure]; 
    } 
} 

class Test { 

    public function hello($greeting) 
    { 
     $closure = function ($message) use ($greeting, &$closure) { 
      echo "Inside: ", ClosureHash::from($closure), PHP_EOL, "<br>" ; 
     }; 
     return $closure; 
    } 
} 

$obj = new Test(); 

$closure = $obj->hello('Hello'); 
$closure('PHP'); 
echo "Outside: ", ClosureHash::from($closure), PHP_EOL, "<br>"; 

$another = $obj->hello('Bonjour'); 
$another('PHP'); 
echo "Outside: ", ClosureHash::from($another), PHP_EOL, "<br>"; 
1

這聽起來像你想要生成一個簽名。如果閉包接受任何參數,則從閉包外部創建簽名幾乎不可能重現。傳入的數據將改變生成的簽名。

$someClass = new SomeClass(); 
$closure1 = $someClass->closure(); 
$closure1_id = md5(print_r($closure1, true)); 

即使您的閉包不接受參數,仍然存在在閉包中存儲和持久化簽名的問題。你可能能夠在閉包內部使用靜態變量來做一些事情,所以它只能初始化一次並保留「簽名」。但是如何檢索它會變得麻煩。

這聽起來像是你想要一個類,而不是一個閉包。它會解決所有這些問題。您可以在實例化過程中傳遞一個「鹽」,並使用salt生成一個簽名(即一個隨機數)。這將使簽名獨一無二。然後,您可以保留該鹽,使用完全相同的構造函數參數(即salt)重新創建一個類,並將其與已創建的類中的文件簽名進行比較。

+0

'var_dump'可能比'print_r'更好。至少在我的本地副本中,'print_r'的輸出始終是「Closure Object()」,而'var_dump'則包含對象編號。 – Charles

+0

同意,var_dump會更好。 –

3

PHP anonymous functions作爲Closure class的實例公開。因爲它們基本上是對象,所以spl_object_hash將在遞交一個時返回唯一標識符。從PHP交互提示:

php > $a = function() { echo "I am A!"; }; 
php > $b = function() { echo "I am B!"; }; 
php > 
php > 
php > echo spl_object_hash($a), "\n", spl_object_hash($b), "\n"; 
000000004f2ef15d000000003b2d5c60 
000000004f2ef15c000000003b2d5c60 

這些標識符可能相同,但他們在中間的一個字母不同。

該標識符僅適用於該請求,因此即使函數和任何變量不發生更改,也期望它在調用之間進行更改。

+0

你試過了嗎? ;) – dualed

+0

是的,我做了,它嚇了我一跳。我忘了它喜歡在標識符的* middle *中添加唯一的位,而不是在末尾。我認爲它每次都返回相同的標識符,但是不正確。唷! – Charles

+0

我喜歡這個,出於某種原因,我認爲我需要反射來取回Closure對象(如我在上面發佈的解決方案:http://stackoverflow.com/a/13984282/360967),但這似乎並不就是這樣。 – Toby

5

好吧,這裏是我能想到的唯一的事情:

<?php 
$f = function() { 
}; 
$rf = new ReflectionFunction($f); 
$pseudounique = $rf->getFileName().$rf->getEndLine(); 
?> 

如果你喜歡,你可以使用MD5或諸如此類的東西散列它。如果從一個字符串但生成的功能,你應該的種子與uniqid()

+0

它們是在同一行中定義的嗎? – dualed

+0

沒關係最後的評論(刪除)。這是迄今爲止最接近的,我正在使用Reflection來處理類似於@ hakre對spl_object_hash()的建議 - 我會繼續嘗試,如果沒有任何結果,我會繼續這樣做,即使它看起來有點hacky – Toby

+0

這是hacky,是的;)告訴我你的目標php版本,雖然,想法傳入;或只是告訴我,如果PHP 5.4是可能的 – dualed

0

可能的解決方法到達與@hakre的幫助和@dualed:

$someClass = new SomeClass(); 

$closure = $someClass->closure(); 
$closure2 = $someClass->closure2(); 

$rf = new ReflectionFunction($closure); 
$rf2 = new ReflectionFunction($closure2); 

print spl_object_hash($rf); // Outputs: 000000007ddc37c8000000003b230216 
print spl_object_hash($rf2); // Outputs: 000000007ddc37c9000000003b230216 

class SomeClass 
{ 
    function closure() 
    { 
     return function() { return 'I am closure: ' . __FUNCTION__; }; 
    } 

    function closure2() 
    { 
     return function() { return 'I am closure: ' . __FUNCTION__; }; 
    } 
} 
+0

但是當你從同一個閉包創建第二個反射函數時它會返回相同的散列嗎?不是我想談談你的解決方案 – dualed

5

您可以將所有你需要寫你自己,你自己的關閉有getId()getHash()或其他。

實施例(Demo):

1: Hello world 
2: Hello world 

第一封閉(ID:1),在ID調用上下文讀出。第二次封閉(ID:2),從封閉內讀取ID(自我引用)。

代碼:

<?php 
/** 
* @link http://stackoverflow.com/questions/13983714/serialize-or-hash-a-closure-in-php 
*/ 

class IdClosure 
{ 
    private $callback; 
    private $id; 

    private static $sequence = 0; 

    final public function __construct(Callable $callback) { 
     $this->callback = $callback; 
     $this->id = ++IdClosure::$sequence; 
    } 

    public function __invoke() { 
     return call_user_func_array($this->callback, func_get_args()); 
    } 

    public function getId() { 
     return $this->id; 
    } 
} 

$hello = new IdClosure(function($text) { echo "Hello $text\n";}); 
echo $hello->getId(), ": ", $hello('world'); 

$hello2 = new IdClosure(function($text) use (&$hello2) { echo $hello2->getId(), ": Hello $text\n";}); 
$hello2('world'); 

我不知道是否適合您的需求,也許它給你一些想法。我建議spl_object_hash,但沒有理解討論太多爲什麼它沒有或最終然後工作。

+0

問題是/如果其他對象已被GC銷燬並收集,函數spl_object_hash可以返回重複值。這是因爲spl_object_hash只對**現有的**對象是唯一的。 (在手冊中這樣說)!這可能永遠不會發生,或者它可能發生很多。我將強烈依賴代碼,因此可能會產生奇怪的效果。 – dualed

+0

@dualed:是的,'spl_object_hash'的行爲是已知的。您需要保留一個對象集合,然後才能使用哈希值,請參閱以下內容:http://php.net/splobjectstorage – hakre

+0

順便說一下:此處的實現沒有重複的閉包ID。 – hakre

2

Superclosure提供了一個方便的類,它允許你序列化/反序列化封閉,除其他事項外。