Skip to content

Commit 951593a

Browse files
authored
feat: introduce Once and Onceable classes, deprecate Cache and Backtrace (#808)
* feat: introduce Once and Onceable classes, deprecate Cache and Backtrace * test: add PHPUnit group attribute to OnceTest class * fix: return null when no valid instance is created in Onceable class --------- Co-authored-by: Deeka Wong <[email protected]>
1 parent e9f45d8 commit 951593a

File tree

5 files changed

+199
-31
lines changed

5 files changed

+199
-31
lines changed

src/Functions.php

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -61,40 +61,19 @@ function retry($times, callable $callback, $sleepMilliseconds = 0, $when = null)
6161
}
6262

6363
/**
64-
* @template TReturn
64+
* Ensures a callable is only called once, and returns the result on subsequent calls.
65+
*
66+
* @template TReturnType
6567
*
66-
* @param (callable(): TReturn) $callback
67-
* @return TReturn
68+
* @param callable(): TReturnType $callback
69+
* @return TReturnType
6870
*/
69-
function once(callable $callback): mixed
71+
function once(callable $callback)
7072
{
71-
$trace = debug_backtrace(
72-
DEBUG_BACKTRACE_PROVIDE_OBJECT,
73-
2
73+
$onceable = Onceable::tryFromTrace(
74+
debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2),
75+
$callback,
7476
);
7577

76-
$backtrace = new Once\Backtrace($trace);
77-
78-
if ($backtrace->getFunctionName() === 'eval') {
79-
return call_user_func($callback);
80-
}
81-
82-
$object = $backtrace->getObject();
83-
$hash = $backtrace->getHash();
84-
$cache = Once\Cache::getInstance();
85-
86-
if (is_string($object)) {
87-
$object = $cache;
88-
}
89-
90-
if (! $cache->isEnabled()) {
91-
return call_user_func($callback, $backtrace->getArguments());
92-
}
93-
94-
if (! $cache->has($object, $hash)) {
95-
$result = call_user_func($callback, $backtrace->getArguments());
96-
$cache->set($object, $hash, $result);
97-
}
98-
99-
return $cache->get($object, $hash);
78+
return $onceable ? Once::instance()->value($onceable) : call_user_func($callback);
10079
}

src/Once.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of friendsofhyperf/components.
6+
*
7+
* @link https://github.com/friendsofhyperf/components
8+
* @document https://github.com/friendsofhyperf/components/blob/main/README.md
9+
* @contact [email protected]
10+
*/
11+
12+
namespace FriendsOfHyperf\Support;
13+
14+
use WeakMap;
15+
16+
class Once
17+
{
18+
/**
19+
* The current globally used instance.
20+
*
21+
* @var static|null
22+
*/
23+
protected static ?self $instance = null;
24+
25+
/**
26+
* Indicates if the once instance is enabled.
27+
*/
28+
protected static bool $enabled = true;
29+
30+
/**
31+
* Create a new once instance.
32+
*
33+
* @param WeakMap<object, array<string, mixed>> $values
34+
*/
35+
protected function __construct(protected WeakMap $values)
36+
{
37+
}
38+
39+
/**
40+
* Create a new once instance.
41+
*
42+
* @return static
43+
*/
44+
public static function instance()
45+
{
46+
return static::$instance ??= new static(new WeakMap());
47+
}
48+
49+
/**
50+
* Get the value of the given onceable.
51+
*
52+
* @return mixed
53+
*/
54+
public function value(Onceable $onceable)
55+
{
56+
if (! static::$enabled) {
57+
return call_user_func($onceable->callable);
58+
}
59+
60+
$object = $onceable->object ?: $this;
61+
62+
$hash = $onceable->hash;
63+
64+
if (! isset($this->values[$object])) {
65+
$this->values[$object] = [];
66+
}
67+
68+
if (array_key_exists($hash, $this->values[$object])) {
69+
return $this->values[$object][$hash];
70+
}
71+
72+
return $this->values[$object][$hash] = call_user_func($onceable->callable);
73+
}
74+
75+
/**
76+
* Re-enable the once instance if it was disabled.
77+
*/
78+
public static function enable()
79+
{
80+
static::$enabled = true;
81+
}
82+
83+
/**
84+
* Disable the once instance.
85+
*/
86+
public static function disable()
87+
{
88+
static::$enabled = false;
89+
}
90+
91+
/**
92+
* Flush the once instance.
93+
*/
94+
public static function flush()
95+
{
96+
static::$instance = null;
97+
}
98+
}

src/Once/Backtrace.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
namespace FriendsOfHyperf\Support\Once;
1313

14+
/**
15+
* @deprecated since v3.1, use FriendsOfHyperf\Support\Onceable instead, will removed in v3.2
16+
*/
1417
class Backtrace
1518
{
1619
protected array $trace;

src/Once/Cache.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
use Countable;
1515
use WeakMap;
1616

17+
/**
18+
* @deprecated since v3.1, use FriendsOfHyperf\Support\Once instead, will removed in v3.2
19+
*/
1720
class Cache implements Countable
1821
{
1922
public WeakMap $values;

src/Onceable.php

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of friendsofhyperf/components.
6+
*
7+
* @link https://github.com/friendsofhyperf/components
8+
* @document https://github.com/friendsofhyperf/components/blob/main/README.md
9+
* @contact [email protected]
10+
*/
11+
12+
namespace FriendsOfHyperf\Support;
13+
14+
use Closure;
15+
use Laravel\SerializableClosure\Support\ReflectionClosure;
16+
17+
class Onceable
18+
{
19+
/**
20+
* Create a new onceable instance.
21+
*
22+
* @param callable $callable
23+
*/
24+
public function __construct(
25+
public string $hash,
26+
public ?object $object,
27+
public $callable,
28+
) {
29+
}
30+
31+
/**
32+
* Tries to create a new onceable instance from the given trace.
33+
*
34+
* @param array<int, array<string, mixed>> $trace
35+
* @return static|null
36+
*/
37+
public static function tryFromTrace(array $trace, callable $callable)
38+
{
39+
if (! is_null($hash = static::hashFromTrace($trace, $callable))) {
40+
$object = static::objectFromTrace($trace);
41+
42+
return new static($hash, $object, $callable);
43+
}
44+
45+
return null;
46+
}
47+
48+
/**
49+
* Computes the object of the onceable from the given trace, if any.
50+
*
51+
* @param array<int, array<string, mixed>> $trace
52+
* @return object|null
53+
*/
54+
protected static function objectFromTrace(array $trace)
55+
{
56+
return $trace[1]['object'] ?? null;
57+
}
58+
59+
/**
60+
* Computes the hash of the onceable from the given trace.
61+
*
62+
* @param array<int, array<string, mixed>> $trace
63+
* @return string|null
64+
*/
65+
protected static function hashFromTrace(array $trace, callable $callable)
66+
{
67+
if (str_contains($trace[0]['file'] ?? '', 'eval()\'d code')) {
68+
return null;
69+
}
70+
71+
$uses = array_map(
72+
fn (mixed $argument) => is_object($argument) ? spl_object_hash($argument) : $argument,
73+
$callable instanceof Closure ? (new ReflectionClosure($callable))->getClosureUsedVariables() : [],
74+
);
75+
76+
return md5(sprintf(
77+
'%s@%s%s:%s (%s)',
78+
$trace[0]['file'],
79+
isset($trace[1]['class']) ? ($trace[1]['class'] . '@') : '',
80+
$trace[1]['function'],
81+
$trace[0]['line'],
82+
serialize($uses),
83+
));
84+
}
85+
}

0 commit comments

Comments
 (0)