Last active
January 13, 2017 13:52
-
-
Save thinkingmedia/84e2ae541785e633bdde2822d5edbc89 to your computer and use it in GitHub Desktop.
Disposable pattern implemented in PHP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Wraps the callable in a try/finally before calling dispose() | |
* | |
* @param GemsDisposable $obj | |
* @param callable $worker | |
* @return mixed | |
*/ | |
function using(GemsDisposable $obj, callable $worker) | |
{ | |
try { | |
return $worker($obj); | |
} finally { | |
if($obj !== null) { | |
$obj->dispose(); | |
unset($obj); | |
} | |
} | |
} | |
/** | |
* Calls dispose if the object is GemsDisposable | |
* | |
* @param mixed $ref | |
* @param string|null $property | |
* @throws Exception | |
*/ | |
function dispose($ref, $property = null) | |
{ | |
if ($property === null) { | |
if ($ref instanceof GemsDisposable) { | |
$ref->dispose(); | |
} | |
return; | |
} | |
if (!isset($ref->$property)) { | |
throw new GemsException(sprintf("dispose(%s,'%s') called, but property does not exist on target object.", get_class($ref), $property)); | |
} | |
if ($ref->$property instanceof GemsDisposable) { | |
$ref->$property->dispose(); | |
} | |
unset($ref->$property); | |
} | |
/** | |
* Inspired by IDisposable on .NET | |
*/ | |
interface GemsDisposable | |
{ | |
/** | |
* Call dispose() on child properties. | |
* Call unset() on object properties to reduce memory leaks. | |
*/ | |
public function dispose(); | |
} | |
/** | |
* Class GemsDisposeTrait | |
* | |
* @mixin GemsDisposable | |
*/ | |
trait GemsDisposeTrait | |
{ | |
/** | |
* @var bool Set to false to disable disposing of array properties. | |
*/ | |
private $dispose_arrays = true; | |
/** | |
* Disposes of all public, protected and private properties. | |
*/ | |
public function dispose() | |
{ | |
$reflector = new \ReflectionClass($this); | |
foreach ($reflector->getProperties() as $property) { | |
$name = $property->getName(); | |
$property->setAccessible(true); | |
$value = $property->getValue($this); | |
if ($value instanceof GemsDisposable) { | |
if ($property->isStatic()) { | |
throw new GemsException("Found a static property that implements GemsDisposable. This usage is not supported."); | |
} | |
if (!$property->isPublic()) { | |
throw new GemsException("Can not auto-dispose of non-public GemsDisposable property: {$reflector->name}::{$name} is {$property->class}"); | |
} | |
} | |
if (is_array($value) && $this->dispose_arrays === true) { | |
array_walk_recursive($value, function($item) { | |
dispose($item); | |
}); | |
} | |
if (is_object($value) && $property->isPublic()) { | |
dispose($this, $name); | |
} | |
} | |
// remove models added by GemsModelsTrait | |
if(method_exists($this, 'disposeModels')) | |
{ | |
$this->disposeModels(); | |
} | |
unset($reflector); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment