Skip to content

Commit

Permalink
Merge pull request #23 from filecage/fastcreator
Browse files Browse the repository at this point in the history
Fastcreator 🏎💨
  • Loading branch information
filecage authored Feb 25, 2021
2 parents be5bf21 + e3b8008 commit 114ead4
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 35 deletions.
17 changes: 16 additions & 1 deletion lib/Creatable.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,29 @@ class Creatable extends InvokableMethod {
*/
private $useConstructor = true;

/**
* To avoid some reflection calls
* @var array
*/
private static $cache = [];

/**
* @param string $className
* @param string|null $creationMethodName
* @return Creatable
* @throws \ReflectionException
*/
static function createFromClassName (string $className, string $creationMethodName = null) : self {
return new static(new \ReflectionClass($className), $creationMethodName);
$cacheKey = $className . '::' . ($creationMethodName ?? '__default');
$cache = static::$cache[$cacheKey] ?? null;
if ($cache) {
return $cache;
}

$creatable = new static(new \ReflectionClass($className), $creationMethodName);
static::$cache[$cacheKey] = $creatable;

return $creatable;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion lib/Creation.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Creation extends Invocation {
*/
function __construct ($className, ResourceRegistry $resourceRegistry, ResourceRegistry $injections = null) {
$this->className = $className;

try {
$this->creatable = Creatable::createFromClassName($this->className);
} catch (\ReflectionException $reflectionException) {
Expand Down Expand Up @@ -74,7 +75,7 @@ private function createInstanceWithRegistry (string $className, Creatable $creat
if ($recreate === true) {
try {
// Does the registry contain a dependency that is required for this resource?
if ($registry->containsAnyOf($creatable->getDependencies())) {
if ($creatable->getDependencies()->containsClassDependency(...$registry->getClassResourceKeys())) {
$instance = $this->createInstance($creatable);
}
} catch (Unresolvable $exception) {
Expand Down
55 changes: 49 additions & 6 deletions lib/Dependency.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ class Dependency {
*/
private $innerDependencies;

/**
* Used for fast dependency tree lookups
* @var bool[string]
*/
private $innerDependenciesFlatHashes;

/**
* Static memoization of class dependencies to speed up lookups for big trees in larger projects
* We're caching by class name as a class can not be overwritten
Expand Down Expand Up @@ -64,7 +70,6 @@ static function yieldFromReflectionFunction (?\ReflectionFunctionAbstract $refle
* @throws \ReflectionException
*/
static function createFromReflectionParameter(\ReflectionParameter $dependencyParameter) : Dependency {
$parameterName = $dependencyParameter->getName();
$type = $dependencyParameter->getType();

// If the parameter refers to a class, we have to find it's inner dependencies
Expand All @@ -74,16 +79,18 @@ static function createFromReflectionParameter(\ReflectionParameter $dependencyPa
if (isset(static::$dependencies[$dependencyClassName])) {
return static::$dependencies[$dependencyClassName];
} elseif (!class_exists($dependencyClassName, false)) {
$dependency = new static(false, $parameterName, $dependencyClassName, null);
$dependency = new static(false, $dependencyParameter->getName(), $dependencyClassName, null);
} else {
$dependency = static::createFromCreatable($parameterName, new Creatable($dependencyParameter->getClass()));
$dependency = static::createFromCreatable($dependencyParameter->getName(), new Creatable($dependencyParameter->getClass()));
static::$dependencies[$dependencyClassName] = $dependency;
}

} else {
$dependency = new static(true, $parameterName, null, null);
$parameterName = $dependencyParameter->getName();
$dependency = new static(true, $parameterName, '__PARAM__' . $parameterName, null);
}

// todo: call as late as possible
if ($dependencyParameter->isDefaultValueAvailable()) {
$dependency->isDefaultValueAvailable = true;
$dependency->defaultValue = $dependencyParameter->getDefaultValue();
Expand Down Expand Up @@ -111,11 +118,16 @@ static function createFromCreatable (string $parameterName, Creatable $creatable
* @param string $dependencyKey
* @param DependencyContainer $innerDependencies
*/
function __construct (bool $isPrimitive, string $parameterName, ?string $dependencyKey, ?DependencyContainer $innerDependencies) {
function __construct (bool $isPrimitive, string $parameterName, string $dependencyKey, ?DependencyContainer $innerDependencies) {
$this->isPrimitive = $isPrimitive;
$this->parameterName = $parameterName;
$this->dependencyKey = $dependencyKey;
$this->innerDependencies = $innerDependencies;
$this->innerDependenciesFlatHashes = [$dependencyKey => true];

if ($innerDependencies !== null) {
static::buildInnerDependencyFlatHashes($innerDependencies, $this->innerDependenciesFlatHashes);
}
}

/**
Expand Down Expand Up @@ -149,7 +161,7 @@ function isPrimitive () : bool {
/**
* @return string
*/
function getDependencyKey () : ?string {
function getDependencyKey () : string {
return $this->dependencyKey;
}

Expand All @@ -167,4 +179,35 @@ function getInnerDependencies () : ?DependencyContainer {
return $this->innerDependencies;
}

/**
* @param string ...$dependencyKeys
*
* @return bool
*/
function isDependencyInTree (string ...$dependencyKeys) : bool {
foreach ($dependencyKeys as $dependencyKey) {
if (isset($this->innerDependenciesFlatHashes[$dependencyKey])) {
return true;
}
}

return false;
}

/**
* @param DependencyContainer $dependencies
* @param array $flatHashMap
*/
private static function buildInnerDependencyFlatHashes (DependencyContainer $dependencies, array &$flatHashMap) : void {
foreach ($dependencies->getDependencies() as $dependency) {
$flatHashMap[$dependency->getDependencyKey()] = true;
$innerDependencies = $dependency->getInnerDependencies();
if ($innerDependencies === null) {
continue;
}

static::buildInnerDependencyFlatHashes($innerDependencies, $flatHashMap);
}
}

}
16 changes: 16 additions & 0 deletions lib/DependencyContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ function getDependencies () : array {
}

/**
* @deprecated deprecated in favor of containsClassDependency
* @return \Generator
*/
function getFlatDependencyIterator () : \Generator {
Expand All @@ -47,6 +48,21 @@ function getFlatDependencyIterator () : \Generator {
}
}

/**
* @param string ...$classNames
*
* @return bool
*/
function containsClassDependency (string ...$classNames) : bool {
foreach ($this->dependencies as $dependency) {
if ($dependency->isDependencyInTree(...$classNames)) {
return true;
}
}

return false;
}

/**
* @param DependencyContainer $dependencyContainer
*
Expand Down
2 changes: 1 addition & 1 deletion lib/Fabrication.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function fabricate () {
$isInjectedInstance = false;

while ($instance instanceof Invokable) {
$isInjectedInstance = $isInjectedInstance ?: $this->injectionRegistry->containsAnyOf($instance->getDependencies());
$isInjectedInstance = $isInjectedInstance ?: $instance->getDependencies()->containsClassDependency(...$this->injectionRegistry->getClassResourceKeys());
$invocation = new Invocation($instance, $this->resourceRegistry, $this->injectionRegistry);
$instance = $invocation->invoke();
}
Expand Down
8 changes: 4 additions & 4 deletions lib/Invokable.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ function getDependencies () {
* @throws \ReflectionException
*/
private function collectDependencies () {
$dependencies = new DependencyContainer();
if (!$this->invokableReflection) {
return $dependencies;
return new DependencyContainer();
}

$dependencies = [];
foreach ($this->invokableReflection->getParameters() as $parameter) {
$dependencies->addDependency(Dependency::createFromReflectionParameter($parameter));
$dependencies[] = Dependency::createFromReflectionParameter($parameter);
}

return $dependencies;
return new DependencyContainer(...$dependencies);
}
}
71 changes: 49 additions & 22 deletions lib/ResourceRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ class ResourceRegistry {
*/
private $classResources = [];

/**
* Keeps all resource keys for easier access without using `array_keys()`
* @var string[]
*/
private $classResourceKeys = [];

/**
* @var array
*/
Expand Down Expand Up @@ -40,7 +46,8 @@ function registerClassResource ($instance, ...$classResourceKeys) {
if (isset($this->classResources[$classResourceKey])) {
continue;
}


$this->classResourceKeys[] = $classResourceKey;
$this->classResources[$classResourceKey] = $resource;
}

Expand All @@ -57,6 +64,9 @@ function registerClassResource ($instance, ...$classResourceKeys) {
*/
function resetClassResource ($classResourceKey) {
unset($this->classResources[$classResourceKey]);
unset($this->classResourceKeys[array_search($classResourceKey, $this->classResourceKeys)]);
// unsetting at the given index is slightly faster than copying the full array
// however it is dangerous if you can not be sure that the index exists (otherwise index 0 will be unset)

return $this;
}
Expand All @@ -74,6 +84,13 @@ function getClassResource ($classResourceKey) {
return $this->classResources[$classResourceKey]->getInstance();
}

/**
* @return string[]
*/
function getClassResourceKeys () : array {
return $this->classResourceKeys;
}

/**
* @param string $classResourceKey
* @param Invokable $factory
Expand Down Expand Up @@ -108,21 +125,15 @@ function getFactoryInvokableForClassResource (string $classResourceKey) : ?Invok
* @return object
*/
function findFulfillingInstance (Creatable $creatable) {
$fulfillable = $creatable->getReflectionClass();
if ($fulfillable->isInterface()) {
$verificationCallback = function(ClassResource $resource) use ($fulfillable) {
return $resource->implementsInterface($fulfillable->getName());
};
} elseif ($fulfillable->isAbstract()) {
$verificationCallback = function(ClassResource $resource) use ($fulfillable) {
return $fulfillable->isInstance($resource->getInstance());
};
} else {
// unsupported uninstantiable
return null;
}

foreach ($this->classResources as $resource) {
if (!isset($verificationCallback)) {
// all reflection calls are expensive, so we're saving them for all registries that are empty anyway
$verificationCallback = $this->createFulfillableAsserter($creatable);
if ($verificationCallback === null) {
return null; // not fulfillable, the class is neither an abstract nor an interface
}
}

if ($verificationCallback($resource) === true) {
return $resource->getInstance();
}
Expand Down Expand Up @@ -171,16 +182,11 @@ function getPrimitiveResource ($resourceKey) {
/**
* @param DependencyContainer $dependencyContainer
*
* @deprecated
* @return bool
*/
function containsAnyOf (DependencyContainer $dependencyContainer) {
foreach ($dependencyContainer->getFlatDependencyIterator() as $dependency) {
if (!$dependency->isPrimitive() && $this->getClassResource($dependency->getDependencyKey())) {
return true;
}
}

return false;
return $dependencyContainer->containsClassDependency(...$this->classResourceKeys);
}

/**
Expand All @@ -204,6 +210,27 @@ function cloneWithout ($exceptedClass) {
return $clone;
}

/**
* @param Creatable $creatable
*
* @return callable|null
*/
private function createFulfillableAsserter (Creatable $creatable) : ?callable {
$fulfillable = $creatable->getReflectionClass();
if ($fulfillable->isInterface()) {
return function(ClassResource $resource) use ($fulfillable) {
return $resource->implementsInterface($fulfillable->getName());
};
} elseif ($fulfillable->isAbstract()) {
return function(ClassResource $resource) use ($fulfillable) {
return $fulfillable->isInstance($resource->getInstance());
};
} else {
// unsupported uninstantiable
return null;
}
}

/**
* @param callable $callback
*
Expand Down

0 comments on commit 114ead4

Please sign in to comment.