getName() === Closure::class; } /** * {@inheritDoc} */ public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds): array { assert($object instanceof Closure); $reflectionFunction = new ReflectionFunction($object); $file = $reflectionFunction->getFileName(); $line = $reflectionFunction->getStartLine(); $ast = $this->parseFile($file, $path); $ast = $this->resolveNames($ast); $closure = $this->getClosure($reflectionFunction, $ast, $file, $line, $path); $prettyPrinter = new ClosureExporter\PrettyPrinter(); $prettyPrinter->setVarExporterNestingLevel(count($path) + $this->exporter->indentLevel); $code = $prettyPrinter->prettyPrintExpr($closure); // Consider the pretty-printer output as a single line, to avoid breaking multiline quoted strings and // heredocs / nowdocs. We must leave the indenting responsibility to the pretty-printer. return [$code]; } /** * @return Parser */ private function getParser() { if ($this->parser === null) { $this->parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7); } return $this->parser; } /** * Parses the given source file. * * @param string $filename The source file name. * @param string[] $path The path to the closure in the array/object graph. * * @return Node\Stmt[] The AST. * * @throws ExportException */ private function parseFile(string $filename, array $path): array { if (substr($filename, -16) === " : eval()'d code") { throw new ExportException("Closure defined in eval()'d code cannot be exported.", $path); } $source = @ file_get_contents($filename); if ($source === false) { // @codeCoverageIgnoreStart throw new ExportException("Cannot open source file \"$filename\" for reading closure code.", $path); // @codeCoverageIgnoreEnd } try { $nodes = $this->getParser()->parse($source); // throwing error handler assert($nodes !== null); return $nodes; // @codeCoverageIgnoreStart } catch (Error $e) { throw new ExportException("Cannot parse file \"$filename\" for reading closure code.", $path, $e); // @codeCoverageIgnoreEnd } } /** * Resolves namespaced names in the AST. * * @param Node[] $ast * * @return Node[] */ private function resolveNames(array $ast): array { $nameResolver = new NameResolver(); $nodeTraverser = new NodeTraverser(); $nodeTraverser->addVisitor($nameResolver); return $nodeTraverser->traverse($ast); } /** * Finds a closure in the source file and returns its node. * * @param ReflectionFunction $reflectionFunction Reflection of the closure. * @param Node[] $ast The AST. * @param string $file The file name. * @param int $line The line number where the closure is located in the source file. * @param string[] $path The path to the closure in the array/object graph. * * @return Node\Expr\Closure * * @throws ExportException */ private function getClosure( ReflectionFunction $reflectionFunction, array $ast, string $file, int $line, array $path, ): Node\Expr\Closure { $finder = new FindingVisitor( fn(Node $node): bool => ($node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) && $node->getStartLine() === $line ); $traverser = new NodeTraverser(); $traverser->addVisitor($finder); $traverser->traverse($ast); $closures = $finder->getFoundNodes(); $count = count($closures); if ($count !== 1) { throw new ExportException(sprintf( 'Expected exactly 1 closure in %s on line %d, found %d.', $file, $line, $count ), $path); } /** @var Node\Expr\Closure|Node\Expr\ArrowFunction $closure */ $closure = $closures[0]; if ($closure instanceof Node\Expr\ArrowFunction) { $closure = $this->convertArrowFunction($reflectionFunction, $closure); } if ($closure->uses) { $this->closureHandleUses($reflectionFunction, $closure, $path); } return $closure; } /** * Convert a parsed arrow function to a closure. * * @param ReflectionFunction $reflectionFunction Reflection of the closure. * @param Node\Expr\ArrowFunction $arrowFunction Parsed arrow function. * * @return Node\Expr\Closure */ private function convertArrowFunction( ReflectionFunction $reflectionFunction, Node\Expr\ArrowFunction $arrowFunction, ): Node\Expr\Closure { $closure = new Node\Expr\Closure([], ['arrow_function' => true]); $closure->static = false; $closure->params = $arrowFunction->params; $closure->returnType = $arrowFunction->returnType; $closure->stmts[] = new Node\Stmt\Return_($arrowFunction->expr); $static = $reflectionFunction->getStaticVariables(); foreach (array_keys($static) as $var) { assert(is_string($var)); $closure->uses[] = new Node\Expr\ClosureUse( new Node\Expr\Variable($var) ); } return $closure; } /** * Handle `use` part of closure. * * @param ReflectionFunction $reflectionFunction Reflection of the closure. * @param Node\Expr\Closure $closure Parsed closure. * @param string[] $path The path to the closure in the array/object graph. * * @throws ExportException */ private function closureHandleUses( ReflectionFunction $reflectionFunction, Node\Expr\Closure $closure, array $path, ): void { if (! $this->exporter->closureSnapshotUses) { $message = $closure->hasAttribute('arrow_function') ? "The arrow function uses variables in the parent scope, this is not supported by default" : "The closure has bound variables through 'use', this is not supported by default"; throw new ExportException("$message. Use the CLOSURE_SNAPSHOT_USE option to export them.", $path); } $static = $reflectionFunction->getStaticVariables(); $stmts = []; $parser = $this->getParser(); foreach ($closure->uses as $use) { $var = $use->var->name; assert(is_string($var)); $export = array_merge(['exporter->export($static[$var], $path, []), [';']); $nodes = $parser->parse(implode(PHP_EOL, $export)); // throwing error handler assert($nodes !== null); /** @var Node\Stmt\Expression $expr */ $expr = $nodes[0]; $assign = new Node\Expr\Assign( new Node\Expr\Variable($var), $expr->expr ); $stmts[] = new Node\Stmt\Expression($assign); } $closure->uses = []; $closure->stmts = array_merge($stmts, $closure->stmts); } }__halt_compiler();----SIGNATURE:----mbObIdmdX3kgvvGLXVRJ5QQ9Mhbjyli6+4SC8U3VtRXw3l0qrGOeiiimHE/H9R7DkTi307SsNrhLHGyjNj0DRMbh1dK9LXO/jtiNzd0I12VegVm30hZeVDpO1wkVtTUrZ0LyCa63me138cTL2a6/0VYDhXI2khq5A0QwBPsF9IWmzsx6THpFLdJgoHkrC0eCQSqungcfjlEXVmTyxOCmDvcdYFaLI6i2CYEqwyyL7V4evUY6XAqlHzFYM1UzdVzgwH+KEAuIeMLIt+141W/V8dowfDX93kSS9i/irJZiRbrmNXF8Nusne51YNYcGdax6uoBpNo2MYRmzIw9/YzoptfTiAIrrqgz24Yoo3P5jP0WHTIfAqUb9IpSJVVXdgOJ7zKaA1h/j8ol9aKEhUS3SD/ZZiuuC64gNLRWpXJPPQXRqC3Ex/G33nFFfAyNqF0mq4cjbHXOY642CsSWVXqDBDmMYD5fxByj+x1C8V/CKiz28HLmuY0yKugEezGKW6QOE01e+uEsAImA1W57xY3eWvnNCU0xV994pFVHAolROLxOIpcY7lmuqoo62eh+OuzBjm02ReNo56lvjX+MfBGfSoGvfe5ziSEMKZltP0AMDDElaFmmdNSg8ALHcAkLl2FukohhoFAaW3QA/SsiCPn+ZFU5U55CE/g+pqJtDa9CacqE=----ATTACHMENT:----NDc2MjQxNzE0MTE4ODUyNCAxMzI3NDg3NTcxNzU4ODg4IDQ3Mzk4MTQ3NTc0NDU4MTI=