Skip to content

Commit

Permalink
feat: introduce Once and Onceable classes, deprecate Cache and Backtr…
Browse files Browse the repository at this point in the history
…ace (#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]>
  • Loading branch information
huangdijia and huangdijia authored Dec 20, 2024
1 parent e9f45d8 commit 951593a
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 31 deletions.
41 changes: 10 additions & 31 deletions src/Functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,40 +61,19 @@ function retry($times, callable $callback, $sleepMilliseconds = 0, $when = null)
}

/**
* @template TReturn
* Ensures a callable is only called once, and returns the result on subsequent calls.
*
* @template TReturnType
*
* @param (callable(): TReturn) $callback
* @return TReturn
* @param callable(): TReturnType $callback
* @return TReturnType
*/
function once(callable $callback): mixed
function once(callable $callback)
{
$trace = debug_backtrace(
DEBUG_BACKTRACE_PROVIDE_OBJECT,
2
$onceable = Onceable::tryFromTrace(
debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2),
$callback,
);

$backtrace = new Once\Backtrace($trace);

if ($backtrace->getFunctionName() === 'eval') {
return call_user_func($callback);
}

$object = $backtrace->getObject();
$hash = $backtrace->getHash();
$cache = Once\Cache::getInstance();

if (is_string($object)) {
$object = $cache;
}

if (! $cache->isEnabled()) {
return call_user_func($callback, $backtrace->getArguments());
}

if (! $cache->has($object, $hash)) {
$result = call_user_func($callback, $backtrace->getArguments());
$cache->set($object, $hash, $result);
}

return $cache->get($object, $hash);
return $onceable ? Once::instance()->value($onceable) : call_user_func($callback);
}
98 changes: 98 additions & 0 deletions src/Once.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);
/**
* This file is part of friendsofhyperf/components.
*
* @link https://github.com/friendsofhyperf/components
* @document https://github.com/friendsofhyperf/components/blob/main/README.md
* @contact [email protected]
*/

namespace FriendsOfHyperf\Support;

use WeakMap;

class Once
{
/**
* The current globally used instance.
*
* @var static|null
*/
protected static ?self $instance = null;

/**
* Indicates if the once instance is enabled.
*/
protected static bool $enabled = true;

/**
* Create a new once instance.
*
* @param WeakMap<object, array<string, mixed>> $values
*/
protected function __construct(protected WeakMap $values)
{
}

/**
* Create a new once instance.
*
* @return static
*/
public static function instance()
{
return static::$instance ??= new static(new WeakMap());
}

/**
* Get the value of the given onceable.
*
* @return mixed
*/
public function value(Onceable $onceable)
{
if (! static::$enabled) {
return call_user_func($onceable->callable);
}

$object = $onceable->object ?: $this;

$hash = $onceable->hash;

if (! isset($this->values[$object])) {
$this->values[$object] = [];
}

if (array_key_exists($hash, $this->values[$object])) {
return $this->values[$object][$hash];
}

return $this->values[$object][$hash] = call_user_func($onceable->callable);
}

/**
* Re-enable the once instance if it was disabled.
*/
public static function enable()
{
static::$enabled = true;
}

/**
* Disable the once instance.
*/
public static function disable()
{
static::$enabled = false;
}

/**
* Flush the once instance.
*/
public static function flush()
{
static::$instance = null;
}
}
3 changes: 3 additions & 0 deletions src/Once/Backtrace.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

namespace FriendsOfHyperf\Support\Once;

/**
* @deprecated since v3.1, use FriendsOfHyperf\Support\Onceable instead, will removed in v3.2
*/
class Backtrace
{
protected array $trace;
Expand Down
3 changes: 3 additions & 0 deletions src/Once/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
use Countable;
use WeakMap;

/**
* @deprecated since v3.1, use FriendsOfHyperf\Support\Once instead, will removed in v3.2
*/
class Cache implements Countable
{
public WeakMap $values;
Expand Down
85 changes: 85 additions & 0 deletions src/Onceable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);
/**
* This file is part of friendsofhyperf/components.
*
* @link https://github.com/friendsofhyperf/components
* @document https://github.com/friendsofhyperf/components/blob/main/README.md
* @contact [email protected]
*/

namespace FriendsOfHyperf\Support;

use Closure;
use Laravel\SerializableClosure\Support\ReflectionClosure;

class Onceable
{
/**
* Create a new onceable instance.
*
* @param callable $callable
*/
public function __construct(
public string $hash,
public ?object $object,
public $callable,
) {
}

/**
* Tries to create a new onceable instance from the given trace.
*
* @param array<int, array<string, mixed>> $trace
* @return static|null
*/
public static function tryFromTrace(array $trace, callable $callable)
{
if (! is_null($hash = static::hashFromTrace($trace, $callable))) {
$object = static::objectFromTrace($trace);

return new static($hash, $object, $callable);
}

return null;
}

/**
* Computes the object of the onceable from the given trace, if any.
*
* @param array<int, array<string, mixed>> $trace
* @return object|null
*/
protected static function objectFromTrace(array $trace)
{
return $trace[1]['object'] ?? null;
}

/**
* Computes the hash of the onceable from the given trace.
*
* @param array<int, array<string, mixed>> $trace
* @return string|null
*/
protected static function hashFromTrace(array $trace, callable $callable)
{
if (str_contains($trace[0]['file'] ?? '', 'eval()\'d code')) {
return null;
}

$uses = array_map(
fn (mixed $argument) => is_object($argument) ? spl_object_hash($argument) : $argument,
$callable instanceof Closure ? (new ReflectionClosure($callable))->getClosureUsedVariables() : [],
);

return md5(sprintf(
'%s@%s%s:%s (%s)',
$trace[0]['file'],
isset($trace[1]['class']) ? ($trace[1]['class'] . '@') : '',
$trace[1]['function'],
$trace[0]['line'],
serialize($uses),
));
}
}

0 comments on commit 951593a

Please sign in to comment.