| <?php |
| |
| /* |
| * This file is part of the Symfony package. |
| * |
| * (c) Fabien Potencier <fabien@symfony.com> |
| * |
| * For the full copyright and license information, please view the LICENSE |
| * file that was distributed with this source code. |
| */ |
| |
| namespace Symfony\Component\VarDumper\Cloner; |
| |
| use Symfony\Component\VarDumper\Caster\Caster; |
| use Symfony\Component\VarDumper\Exception\ThrowingCasterException; |
| |
| /** |
| * AbstractCloner implements a generic caster mechanism for objects and resources. |
| * |
| * @author Nicolas Grekas <p@tchwork.com> |
| */ |
| abstract class AbstractCloner implements ClonerInterface |
| { |
| public static $defaultCasters = [ |
| '__PHP_Incomplete_Class' => ['Symfony\Component\VarDumper\Caster\Caster', 'castPhpIncompleteClass'], |
| |
| 'Symfony\Component\VarDumper\Caster\CutStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], |
| 'Symfony\Component\VarDumper\Caster\CutArrayStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'], |
| 'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], |
| 'Symfony\Component\VarDumper\Caster\EnumStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'], |
| |
| 'Closure' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'], |
| 'Generator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'], |
| 'ReflectionType' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'], |
| 'ReflectionAttribute' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castAttribute'], |
| 'ReflectionGenerator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'], |
| 'ReflectionClass' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'], |
| 'ReflectionClassConstant' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClassConstant'], |
| 'ReflectionFunctionAbstract' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'], |
| 'ReflectionMethod' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'], |
| 'ReflectionParameter' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'], |
| 'ReflectionProperty' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castProperty'], |
| 'ReflectionReference' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReference'], |
| 'ReflectionExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castExtension'], |
| 'ReflectionZendExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castZendExtension'], |
| |
| 'Doctrine\Common\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], |
| 'Doctrine\Common\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castCommonProxy'], |
| 'Doctrine\ORM\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castOrmProxy'], |
| 'Doctrine\ORM\PersistentCollection' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castPersistentCollection'], |
| 'Doctrine\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], |
| |
| 'DOMException' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'], |
| 'DOMStringList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], |
| 'DOMNameList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], |
| 'DOMImplementation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'], |
| 'DOMImplementationList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], |
| 'DOMNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'], |
| 'DOMNameSpaceNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNameSpaceNode'], |
| 'DOMDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocument'], |
| 'DOMNodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], |
| 'DOMNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], |
| 'DOMCharacterData' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'], |
| 'DOMAttr' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'], |
| 'DOMElement' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'], |
| 'DOMText' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'], |
| 'DOMTypeinfo' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castTypeinfo'], |
| 'DOMDomError' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDomError'], |
| 'DOMLocator' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLocator'], |
| 'DOMDocumentType' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'], |
| 'DOMNotation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'], |
| 'DOMEntity' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'], |
| 'DOMProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'], |
| 'DOMXPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'], |
| |
| 'XMLReader' => ['Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'], |
| |
| 'ErrorException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castErrorException'], |
| 'Exception' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castException'], |
| 'Error' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castError'], |
| 'Symfony\Bridge\Monolog\Logger' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], |
| 'Symfony\Component\DependencyInjection\ContainerInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], |
| 'Symfony\Component\EventDispatcher\EventDispatcherInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], |
| 'Symfony\Component\HttpClient\CurlHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], |
| 'Symfony\Component\HttpClient\NativeHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], |
| 'Symfony\Component\HttpClient\Response\CurlResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], |
| 'Symfony\Component\HttpClient\Response\NativeResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], |
| 'Symfony\Component\HttpFoundation\Request' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'], |
| 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'], |
| 'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'], |
| 'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'], |
| 'Symfony\Component\VarDumper\Cloner\AbstractCloner' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], |
| 'Symfony\Component\ErrorHandler\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'], |
| |
| 'Imagine\Image\ImageInterface' => ['Symfony\Component\VarDumper\Caster\ImagineCaster', 'castImage'], |
| |
| 'Ramsey\Uuid\UuidInterface' => ['Symfony\Component\VarDumper\Caster\UuidCaster', 'castRamseyUuid'], |
| |
| 'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'], |
| 'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], |
| 'PHPUnit\Framework\MockObject\MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], |
| 'PHPUnit\Framework\MockObject\Stub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], |
| 'Prophecy\Prophecy\ProphecySubjectInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], |
| 'Mockery\MockInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], |
| |
| 'PDO' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdo'], |
| 'PDOStatement' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdoStatement'], |
| |
| 'AMQPConnection' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castConnection'], |
| 'AMQPChannel' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castChannel'], |
| 'AMQPQueue' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castQueue'], |
| 'AMQPExchange' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castExchange'], |
| 'AMQPEnvelope' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'], |
| |
| 'ArrayObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'], |
| 'ArrayIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayIterator'], |
| 'SplDoublyLinkedList' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'], |
| 'SplFileInfo' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'], |
| 'SplFileObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'], |
| 'SplHeap' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], |
| 'SplObjectStorage' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'], |
| 'SplPriorityQueue' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], |
| 'OuterIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'], |
| 'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'], |
| |
| 'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], |
| 'RedisArray' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'], |
| 'RedisCluster' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisCluster'], |
| |
| 'DateTimeInterface' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'], |
| 'DateInterval' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'], |
| 'DateTimeZone' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castTimeZone'], |
| 'DatePeriod' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castPeriod'], |
| |
| 'GMP' => ['Symfony\Component\VarDumper\Caster\GmpCaster', 'castGmp'], |
| |
| 'MessageFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castMessageFormatter'], |
| 'NumberFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castNumberFormatter'], |
| 'IntlTimeZone' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlTimeZone'], |
| 'IntlCalendar' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlCalendar'], |
| 'IntlDateFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlDateFormatter'], |
| |
| 'Memcached' => ['Symfony\Component\VarDumper\Caster\MemcachedCaster', 'castMemcached'], |
| |
| 'Ds\Collection' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castCollection'], |
| 'Ds\Map' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castMap'], |
| 'Ds\Pair' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPair'], |
| 'Symfony\Component\VarDumper\Caster\DsPairStub' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPairStub'], |
| |
| 'CurlHandle' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], |
| ':curl' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], |
| |
| ':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], |
| ':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], |
| |
| 'GdImage' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], |
| ':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], |
| |
| ':mysql link' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castMysqlLink'], |
| ':pgsql large object' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'], |
| ':pgsql link' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], |
| ':pgsql link persistent' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], |
| ':pgsql result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'], |
| ':process' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'], |
| ':stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], |
| |
| 'OpenSSLCertificate' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], |
| ':OpenSSL X.509' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], |
| |
| ':persistent stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], |
| ':stream-context' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'], |
| |
| 'XmlParser' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], |
| ':xml' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], |
| |
| 'RdKafka' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castRdKafka'], |
| 'RdKafka\Conf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castConf'], |
| 'RdKafka\KafkaConsumer' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castKafkaConsumer'], |
| 'RdKafka\Metadata\Broker' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castBrokerMetadata'], |
| 'RdKafka\Metadata\Collection' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castCollectionMetadata'], |
| 'RdKafka\Metadata\Partition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castPartitionMetadata'], |
| 'RdKafka\Metadata\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicMetadata'], |
| 'RdKafka\Message' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castMessage'], |
| 'RdKafka\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopic'], |
| 'RdKafka\TopicPartition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicPartition'], |
| 'RdKafka\TopicConf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicConf'], |
| ]; |
| |
| protected $maxItems = 2500; |
| protected $maxString = -1; |
| protected $minDepth = 1; |
| |
| private $casters = []; |
| private $prevErrorHandler; |
| private $classInfo = []; |
| private $filter = 0; |
| |
| /** |
| * @param callable[]|null $casters A map of casters |
| * |
| * @see addCasters |
| */ |
| public function __construct(array $casters = null) |
| { |
| if (null === $casters) { |
| $casters = static::$defaultCasters; |
| } |
| $this->addCasters($casters); |
| } |
| |
| /** |
| * Adds casters for resources and objects. |
| * |
| * Maps resources or objects types to a callback. |
| * Types are in the key, with a callable caster for value. |
| * Resource types are to be prefixed with a `:`, |
| * see e.g. static::$defaultCasters. |
| * |
| * @param callable[] $casters A map of casters |
| */ |
| public function addCasters(array $casters) |
| { |
| foreach ($casters as $type => $callback) { |
| $this->casters[$type][] = $callback; |
| } |
| } |
| |
| /** |
| * Sets the maximum number of items to clone past the minimum depth in nested structures. |
| */ |
| public function setMaxItems(int $maxItems) |
| { |
| $this->maxItems = $maxItems; |
| } |
| |
| /** |
| * Sets the maximum cloned length for strings. |
| */ |
| public function setMaxString(int $maxString) |
| { |
| $this->maxString = $maxString; |
| } |
| |
| /** |
| * Sets the minimum tree depth where we are guaranteed to clone all the items. After this |
| * depth is reached, only setMaxItems items will be cloned. |
| */ |
| public function setMinDepth(int $minDepth) |
| { |
| $this->minDepth = $minDepth; |
| } |
| |
| /** |
| * Clones a PHP variable. |
| * |
| * @param mixed $var Any PHP variable |
| * @param int $filter A bit field of Caster::EXCLUDE_* constants |
| * |
| * @return Data The cloned variable represented by a Data object |
| */ |
| public function cloneVar($var, int $filter = 0) |
| { |
| $this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) { |
| if (\E_RECOVERABLE_ERROR === $type || \E_USER_ERROR === $type) { |
| // Cloner never dies |
| throw new \ErrorException($msg, 0, $type, $file, $line); |
| } |
| |
| if ($this->prevErrorHandler) { |
| return ($this->prevErrorHandler)($type, $msg, $file, $line, $context); |
| } |
| |
| return false; |
| }); |
| $this->filter = $filter; |
| |
| if ($gc = gc_enabled()) { |
| gc_disable(); |
| } |
| try { |
| return new Data($this->doClone($var)); |
| } finally { |
| if ($gc) { |
| gc_enable(); |
| } |
| restore_error_handler(); |
| $this->prevErrorHandler = null; |
| } |
| } |
| |
| /** |
| * Effectively clones the PHP variable. |
| * |
| * @param mixed $var Any PHP variable |
| * |
| * @return array The cloned variable represented in an array |
| */ |
| abstract protected function doClone($var); |
| |
| /** |
| * Casts an object to an array representation. |
| * |
| * @param bool $isNested True if the object is nested in the dumped structure |
| * |
| * @return array The object casted as array |
| */ |
| protected function castObject(Stub $stub, bool $isNested) |
| { |
| $obj = $stub->value; |
| $class = $stub->class; |
| |
| if (\PHP_VERSION_ID < 80000 ? "\0" === ($class[15] ?? null) : str_contains($class, "@anonymous\0")) { |
| $stub->class = get_debug_type($obj); |
| } |
| if (isset($this->classInfo[$class])) { |
| [$i, $parents, $hasDebugInfo, $fileInfo] = $this->classInfo[$class]; |
| } else { |
| $i = 2; |
| $parents = [$class]; |
| $hasDebugInfo = method_exists($class, '__debugInfo'); |
| |
| foreach (class_parents($class) as $p) { |
| $parents[] = $p; |
| ++$i; |
| } |
| foreach (class_implements($class) as $p) { |
| $parents[] = $p; |
| ++$i; |
| } |
| $parents[] = '*'; |
| |
| $r = new \ReflectionClass($class); |
| $fileInfo = $r->isInternal() || $r->isSubclassOf(Stub::class) ? [] : [ |
| 'file' => $r->getFileName(), |
| 'line' => $r->getStartLine(), |
| ]; |
| |
| $this->classInfo[$class] = [$i, $parents, $hasDebugInfo, $fileInfo]; |
| } |
| |
| $stub->attr += $fileInfo; |
| $a = Caster::castObject($obj, $class, $hasDebugInfo, $stub->class); |
| |
| try { |
| while ($i--) { |
| if (!empty($this->casters[$p = $parents[$i]])) { |
| foreach ($this->casters[$p] as $callback) { |
| $a = $callback($obj, $a, $stub, $isNested, $this->filter); |
| } |
| } |
| } |
| } catch (\Exception $e) { |
| $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; |
| } |
| |
| return $a; |
| } |
| |
| /** |
| * Casts a resource to an array representation. |
| * |
| * @param bool $isNested True if the object is nested in the dumped structure |
| * |
| * @return array The resource casted as array |
| */ |
| protected function castResource(Stub $stub, bool $isNested) |
| { |
| $a = []; |
| $res = $stub->value; |
| $type = $stub->class; |
| |
| try { |
| if (!empty($this->casters[':'.$type])) { |
| foreach ($this->casters[':'.$type] as $callback) { |
| $a = $callback($res, $a, $stub, $isNested, $this->filter); |
| } |
| } |
| } catch (\Exception $e) { |
| $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; |
| } |
| |
| return $a; |
| } |
| } |