1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Zenstruck\Foundry; |
4
|
|
|
|
5
|
|
|
use Doctrine\ORM\EntityManagerInterface; |
6
|
|
|
use ProxyManager\Factory\AccessInterceptorValueHolderFactory; |
7
|
|
|
|
8
|
|
|
class ProxyGenerator |
9
|
|
|
{ |
10
|
|
|
/** @var Configuration */ |
11
|
|
|
private $configuration; |
12
|
|
|
/** @var AccessInterceptorValueHolderFactory */ |
13
|
|
|
private $factory; |
14
|
|
|
|
15
|
|
|
public function __construct(Configuration $configuration, ?AccessInterceptorValueHolderFactory $factory = null) |
16
|
|
|
{ |
17
|
|
|
$this->configuration = $configuration; |
18
|
|
|
$this->factory = $factory ?? new AccessInterceptorValueHolderFactory(); |
19
|
|
|
} |
20
|
|
|
|
21
|
|
|
public function generate(object $object, array $methods = []): object |
22
|
|
|
{ |
23
|
|
|
$objectManager = $this->configuration->objectManagerFor(\get_class($object)); |
24
|
|
|
$interceptor = function (object $proxy, object $model) use ($objectManager): void { |
25
|
|
|
$autoRefreshEnabled = property_exists($model, '_foundry_autoRefresh') ? $model->_foundry_autoRefresh : true; |
26
|
|
|
if (!$autoRefreshEnabled) { |
27
|
|
|
return; |
28
|
|
|
} |
29
|
|
|
|
30
|
|
|
// only check for changes if the object is managed in the current om |
31
|
|
|
if ($objectManager instanceof EntityManagerInterface && $objectManager->contains($model)) { |
32
|
|
|
// cannot use UOW::recomputeSingleEntityChangeSet() here as it wrongly computes embedded objects as changed |
33
|
|
|
$objectManager->getUnitOfWork()->computeChangeSet($objectManager->getClassMetadata(\get_class($model)), $model); |
34
|
|
|
|
35
|
|
|
if (!empty($objectManager->getUnitOfWork()->getEntityChangeSet($model))) { |
36
|
|
|
throw new \RuntimeException(\sprintf('Cannot auto refresh "%s" as there are unsaved changes. Be sure to call ->save() or disable auto refreshing (see https://github.com/zenstruck/foundry#auto-refresh for details).', \get_class($model))); |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
$objectManager->refresh($model); |
40
|
|
|
} |
41
|
|
|
}; |
42
|
|
|
|
43
|
|
|
$methodsToIntercept = $this->filterGlob(get_class_methods($object), $methods); |
44
|
|
|
|
45
|
|
|
return $this->factory->createProxy($object, array_fill_keys($methodsToIntercept, $interceptor)); |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
private function filterGlob(array $objectMethods, array $methodFilter): array |
49
|
|
|
{ |
50
|
|
|
$methods = []; |
51
|
|
|
if (!$methodFilter) { |
|
|
|
|
52
|
|
|
$methods = $objectMethods; |
53
|
|
|
} else { |
54
|
|
|
foreach ($methodFilter as $filter) { |
55
|
|
|
// no *, assume filter is an exact match and no glob |
56
|
|
|
if (false === strpos($filter, '*')){ |
57
|
|
|
$methods[] = $filter; |
58
|
|
|
continue; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
// transform glob (e.g. "get*") into a regex |
62
|
|
|
$methodNameRegex = '/^'.str_replace('*', '[[:alnum:]]+', $filter).'$/'; |
63
|
|
|
$methods = array_merge($methods, array_filter($objectMethods, function ($objectMethod) use ($methodNameRegex) { |
64
|
|
|
return 1 !== preg_match($methodNameRegex, $objectMethod); |
65
|
|
|
})); |
66
|
|
|
} |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
return array_unique($methods); |
70
|
|
|
} |
71
|
|
|
} |
72
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.