Skip to content

Commit

Permalink
Adds once memoization function (#550)
Browse files Browse the repository at this point in the history
* Adds once memoization function

* Delete test-results cache file

* Add .phpunit.cache to .gitignore

* Add null return statement in Onceable::create() method

---------

Co-authored-by: Deeka Wong <[email protected]>
  • Loading branch information
huangdijia and huangdijia committed Feb 4, 2024
1 parent f4a3220 commit a57b9d1
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 0 deletions.
18 changes: 18 additions & 0 deletions src/Functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,21 @@ function retry($times, callable $callback, $sleepMilliseconds = 0, $when = null)
goto beginning;
}
}

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

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][$hash])) {
return $this->values[$object][$hash];
}

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

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;
}
}
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|null $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 a57b9d1

Please sign in to comment.