586 lines
16 KiB
PHP
Executable File
586 lines
16 KiB
PHP
Executable File
<?php
|
|
|
|
namespace Laminas\Code\Generator;
|
|
|
|
use Laminas\Code\DeclareStatement;
|
|
use Laminas\Code\Exception\InvalidArgumentException;
|
|
use Laminas\Code\Generator\Exception\ClassNotFoundException;
|
|
use Traversable;
|
|
|
|
use function array_key_exists;
|
|
use function array_keys;
|
|
use function array_map;
|
|
use function array_merge;
|
|
use function count;
|
|
use function current;
|
|
use function dirname;
|
|
use function file_put_contents;
|
|
use function in_array;
|
|
use function is_array;
|
|
use function is_string;
|
|
use function is_writable;
|
|
use function method_exists;
|
|
use function preg_match;
|
|
use function preg_replace;
|
|
use function property_exists;
|
|
use function reset;
|
|
use function sprintf;
|
|
use function str_repeat;
|
|
use function str_replace;
|
|
use function strrpos;
|
|
use function strtolower;
|
|
use function substr;
|
|
use function token_get_all;
|
|
|
|
use const T_COMMENT;
|
|
use const T_DOC_COMMENT;
|
|
use const T_OPEN_TAG;
|
|
use const T_WHITESPACE;
|
|
|
|
class FileGenerator extends AbstractGenerator
|
|
{
|
|
protected string $filename = '';
|
|
|
|
protected ?DocBlockGenerator $docBlock = null;
|
|
|
|
/** @var string[] */
|
|
protected array $requiredFiles = [];
|
|
|
|
protected string $namespace = '';
|
|
|
|
/** @psalm-var list<array{string, string|null}> */
|
|
protected array $uses = [];
|
|
|
|
/**
|
|
* @var ClassGenerator[]
|
|
* @psalm-var array<string, ClassGenerator>
|
|
*/
|
|
protected array $classes = [];
|
|
|
|
protected string $body = '';
|
|
|
|
/**
|
|
* @var DeclareStatement[]
|
|
* @psalm-var array<string, DeclareStatement>
|
|
*/
|
|
protected array $declares = [];
|
|
|
|
/**
|
|
* Passes $options to {@link setOptions()}.
|
|
*
|
|
* @param array|Traversable|null $options
|
|
*/
|
|
public function __construct($options = null)
|
|
{
|
|
if (null !== $options) {
|
|
$this->setOptions($options);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array $values
|
|
* @return FileGenerator
|
|
*/
|
|
public static function fromArray(array $values)
|
|
{
|
|
$fileGenerator = new static();
|
|
foreach ($values as $name => $value) {
|
|
switch (strtolower(str_replace(['.', '-', '_'], '', $name))) {
|
|
case 'filename':
|
|
$fileGenerator->setFilename($value);
|
|
break;
|
|
case 'class':
|
|
$fileGenerator->setClass(
|
|
$value instanceof ClassGenerator
|
|
? $value
|
|
: ClassGenerator::fromArray($value)
|
|
);
|
|
break;
|
|
case 'requiredfiles':
|
|
$fileGenerator->setRequiredFiles($value);
|
|
break;
|
|
case 'declares':
|
|
$fileGenerator->setDeclares(
|
|
array_map(static fn($directive, $value) =>
|
|
DeclareStatement::fromArray([$directive => $value]), array_keys($value), $value)
|
|
);
|
|
break;
|
|
default:
|
|
if (property_exists($fileGenerator, $name)) {
|
|
$fileGenerator->{$name} = $value;
|
|
} elseif (method_exists($fileGenerator, 'set' . $name)) {
|
|
$fileGenerator->{'set' . $name}($value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $fileGenerator;
|
|
}
|
|
|
|
/**
|
|
* @param DocBlockGenerator|array|string $docBlock
|
|
* @throws Exception\InvalidArgumentException
|
|
* @return FileGenerator
|
|
*/
|
|
public function setDocBlock($docBlock)
|
|
{
|
|
if (is_string($docBlock)) {
|
|
$docBlock = ['shortDescription' => $docBlock];
|
|
}
|
|
|
|
if (is_array($docBlock)) {
|
|
$docBlock = new DocBlockGenerator($docBlock);
|
|
} elseif (! $docBlock instanceof DocBlockGenerator) {
|
|
throw new Exception\InvalidArgumentException(sprintf(
|
|
'%s is expecting either a string, array or an instance of %s\DocBlockGenerator',
|
|
__METHOD__,
|
|
__NAMESPACE__
|
|
));
|
|
}
|
|
|
|
$this->docBlock = $docBlock;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return ?DocBlockGenerator
|
|
*/
|
|
public function getDocBlock()
|
|
{
|
|
return $this->docBlock;
|
|
}
|
|
|
|
/**
|
|
* @param string[] $requiredFiles
|
|
* @return FileGenerator
|
|
*/
|
|
public function setRequiredFiles(array $requiredFiles)
|
|
{
|
|
$this->requiredFiles = $requiredFiles;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getRequiredFiles()
|
|
{
|
|
return $this->requiredFiles;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getNamespace()
|
|
{
|
|
return $this->namespace;
|
|
}
|
|
|
|
/**
|
|
* @param string $namespace
|
|
* @return FileGenerator
|
|
*/
|
|
public function setNamespace($namespace)
|
|
{
|
|
$this->namespace = (string) $namespace;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Returns an array with the first element the use statement, second is the as part.
|
|
* If $withResolvedAs is set to true, there will be a third element that is the
|
|
* "resolved" as statement, as the second part is not required in use statements
|
|
*
|
|
* @param bool $withResolvedAs
|
|
* @return array
|
|
* @psalm-return array<int, array{string, null|string, false|null|string}>
|
|
*/
|
|
public function getUses($withResolvedAs = false)
|
|
{
|
|
$uses = $this->uses;
|
|
if ($withResolvedAs) {
|
|
for ($useIndex = 0, $count = count($uses); $useIndex < $count; $useIndex++) {
|
|
if ($uses[$useIndex][1] == '') {
|
|
if (($lastSeparator = strrpos($uses[$useIndex][0], '\\')) !== false) {
|
|
$uses[$useIndex][2] = substr($uses[$useIndex][0], $lastSeparator + 1);
|
|
} else {
|
|
$uses[$useIndex][2] = $uses[$useIndex][0];
|
|
}
|
|
} else {
|
|
$uses[$useIndex][2] = $uses[$useIndex][1];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $uses;
|
|
}
|
|
|
|
/**
|
|
* @param array $uses
|
|
* @return FileGenerator
|
|
*/
|
|
public function setUses(array $uses)
|
|
{
|
|
foreach ($uses as $use) {
|
|
$use = (array) $use;
|
|
if (array_key_exists('use', $use) && array_key_exists('as', $use)) {
|
|
$import = $use['use'];
|
|
$alias = $use['as'];
|
|
} elseif (count($use) == 2) {
|
|
[$import, $alias] = $use;
|
|
} else {
|
|
$import = current($use);
|
|
$alias = null;
|
|
}
|
|
$this->setUse($import, $alias);
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param string $use
|
|
* @param null|string $as
|
|
* @return FileGenerator
|
|
*/
|
|
public function setUse($use, $as = null)
|
|
{
|
|
if (! in_array([$use, $as], $this->uses)) {
|
|
$this->uses[] = [$use, $as];
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param array[]|string[]|ClassGenerator[] $classes
|
|
* @return FileGenerator
|
|
*/
|
|
public function setClasses(array $classes)
|
|
{
|
|
foreach ($classes as $class) {
|
|
$this->setClass($class);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param string|null $name
|
|
* @return ClassGenerator
|
|
* @throws ClassNotFoundException
|
|
*/
|
|
public function getClass($name = null)
|
|
{
|
|
if ($name === null) {
|
|
reset($this->classes);
|
|
$class = current($this->classes);
|
|
if (false === $class) {
|
|
throw new ClassNotFoundException('No class is set');
|
|
}
|
|
|
|
return $class;
|
|
}
|
|
|
|
if (false === array_key_exists($name, $this->classes)) {
|
|
throw new ClassNotFoundException(sprintf('Class %s is not set', $name));
|
|
}
|
|
|
|
return $this->classes[$name];
|
|
}
|
|
|
|
/**
|
|
* @param array|string|ClassGenerator $class
|
|
* @throws Exception\InvalidArgumentException
|
|
* @return FileGenerator
|
|
*/
|
|
public function setClass($class)
|
|
{
|
|
if (is_array($class)) {
|
|
$class = ClassGenerator::fromArray($class);
|
|
} elseif (is_string($class)) {
|
|
$class = new ClassGenerator($class);
|
|
} elseif (! $class instanceof ClassGenerator) {
|
|
throw new Exception\InvalidArgumentException(sprintf(
|
|
'%s is expecting either a string, array or an instance of %s\ClassGenerator',
|
|
__METHOD__,
|
|
__NAMESPACE__
|
|
));
|
|
}
|
|
|
|
// @todo check for dup here
|
|
$className = $class->getName();
|
|
$this->classes[$className] = $class;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param string $filename
|
|
* @return FileGenerator
|
|
*/
|
|
public function setFilename($filename)
|
|
{
|
|
$this->filename = (string) $filename;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getFilename()
|
|
{
|
|
return $this->filename;
|
|
}
|
|
|
|
/**
|
|
* @return ClassGenerator[]
|
|
*/
|
|
public function getClasses()
|
|
{
|
|
return $this->classes;
|
|
}
|
|
|
|
/**
|
|
* @param string $body
|
|
* @return FileGenerator
|
|
*/
|
|
public function setBody($body)
|
|
{
|
|
$this->body = (string) $body;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getBody()
|
|
{
|
|
return $this->body;
|
|
}
|
|
|
|
/**
|
|
* @param DeclareStatement[] $declares
|
|
* @return static
|
|
*/
|
|
public function setDeclares(array $declares)
|
|
{
|
|
foreach ($declares as $declare) {
|
|
if (! $declare instanceof DeclareStatement) {
|
|
throw new InvalidArgumentException(sprintf(
|
|
'%s is expecting an array of %s objects',
|
|
__METHOD__,
|
|
DeclareStatement::class
|
|
));
|
|
}
|
|
|
|
if (! array_key_exists($declare->getDirective(), $this->declares)) {
|
|
$this->declares[$declare->getDirective()] = $declare;
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function isSourceDirty()
|
|
{
|
|
$docBlock = $this->getDocBlock();
|
|
if ($docBlock && $docBlock->isSourceDirty()) {
|
|
return true;
|
|
}
|
|
|
|
foreach ($this->classes as $class) {
|
|
if ($class->isSourceDirty()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return parent::isSourceDirty();
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function generate()
|
|
{
|
|
if ($this->isSourceDirty() === false) {
|
|
return $this->sourceContent ?? '';
|
|
}
|
|
|
|
$output = '';
|
|
|
|
// @note body gets populated when FileGenerator created
|
|
// from a file. @see fromReflection and may also be set
|
|
// via FileGenerator::setBody
|
|
$body = $this->getBody();
|
|
|
|
// start with the body (if there), or open tag
|
|
if (preg_match('#(?:\s*)<\?php#', $body) == false) {
|
|
$output = '<?php' . self::LINE_FEED;
|
|
}
|
|
|
|
// if there are markers, put the body into the output
|
|
if (preg_match('#/\* Laminas_Code_Generator_Php_File-(.*?)Marker:#m', $body)) {
|
|
$tokens = token_get_all($body);
|
|
foreach ($tokens as $token) {
|
|
if (is_array($token) && in_array($token[0], [T_OPEN_TAG, T_COMMENT, T_DOC_COMMENT, T_WHITESPACE])) {
|
|
$output .= $token[1];
|
|
}
|
|
}
|
|
$body = '';
|
|
}
|
|
|
|
// Add file DocBlock, if any
|
|
if (null !== ($docBlock = $this->getDocBlock())) {
|
|
$docBlock->setIndentation('');
|
|
|
|
if (preg_match('#/\* Laminas_Code_Generator_FileGenerator-DocBlockMarker \*/#m', $output)) {
|
|
// @codingStandardsIgnoreStart
|
|
$output = preg_replace('#/\* Laminas_Code_Generator_FileGenerator-DocBlockMarker \*/#m', $docBlock->generate(), $output, 1);
|
|
// @codingStandardsIgnoreEnd
|
|
} else {
|
|
$output .= $docBlock->generate() . self::LINE_FEED;
|
|
}
|
|
}
|
|
|
|
// newline
|
|
$output .= self::LINE_FEED;
|
|
|
|
// declares, if any
|
|
if ($this->declares) {
|
|
$declareStatements = '';
|
|
|
|
foreach ($this->declares as $declare) {
|
|
$declareStatements .= $declare->getStatement() . self::LINE_FEED;
|
|
}
|
|
|
|
if (preg_match('#/\* Laminas_Code_Generator_FileGenerator-DeclaresMarker \*/#m', $output)) {
|
|
$output = preg_replace(
|
|
'#/\* Laminas_Code_Generator_FileGenerator-DeclaresMarker \*/#m',
|
|
$declareStatements,
|
|
$output,
|
|
1
|
|
);
|
|
} else {
|
|
$output .= $declareStatements;
|
|
}
|
|
|
|
$output .= self::LINE_FEED;
|
|
}
|
|
|
|
// namespace, if any
|
|
$namespace = $this->getNamespace();
|
|
if ($namespace) {
|
|
$namespace = sprintf('namespace %s;%s', $namespace, str_repeat(self::LINE_FEED, 2));
|
|
if (preg_match('#/\* Laminas_Code_Generator_FileGenerator-NamespaceMarker \*/#m', $output)) {
|
|
$output = preg_replace(
|
|
'#/\* Laminas_Code_Generator_FileGenerator-NamespaceMarker \*/#m',
|
|
$namespace,
|
|
$output,
|
|
1
|
|
);
|
|
} else {
|
|
$output .= $namespace;
|
|
}
|
|
}
|
|
|
|
// process required files
|
|
// @todo marker replacement for required files
|
|
$requiredFiles = $this->getRequiredFiles();
|
|
if (! empty($requiredFiles)) {
|
|
foreach ($requiredFiles as $requiredFile) {
|
|
$output .= 'require_once \'' . $requiredFile . '\';' . self::LINE_FEED;
|
|
}
|
|
|
|
$output .= self::LINE_FEED;
|
|
}
|
|
|
|
$classes = $this->getClasses();
|
|
$classUses = [];
|
|
//build uses array
|
|
foreach ($classes as $class) {
|
|
//check for duplicate use statements
|
|
$uses = $class->getUses();
|
|
if (! empty($uses) && is_array($uses)) {
|
|
$classUses = array_merge($classUses, $uses);
|
|
}
|
|
}
|
|
|
|
// process import statements
|
|
$uses = $this->getUses();
|
|
if (! empty($uses)) {
|
|
$useOutput = '';
|
|
|
|
foreach ($uses as $use) {
|
|
[$import, $alias] = $use;
|
|
if (null === $alias) {
|
|
$tempOutput = sprintf('%s', $import);
|
|
} else {
|
|
$tempOutput = sprintf('%s as %s', $import, $alias);
|
|
}
|
|
|
|
//don't duplicate use statements
|
|
if (! in_array($tempOutput, $classUses)) {
|
|
$useOutput .= 'use ' . $tempOutput . ';';
|
|
$useOutput .= self::LINE_FEED;
|
|
}
|
|
}
|
|
$useOutput .= self::LINE_FEED;
|
|
|
|
if (preg_match('#/\* Laminas_Code_Generator_FileGenerator-UseMarker \*/#m', $output)) {
|
|
$output = preg_replace(
|
|
'#/\* Laminas_Code_Generator_FileGenerator-UseMarker \*/#m',
|
|
$useOutput,
|
|
$output,
|
|
1
|
|
);
|
|
} else {
|
|
$output .= $useOutput;
|
|
}
|
|
}
|
|
|
|
// process classes
|
|
if (! empty($classes)) {
|
|
foreach ($classes as $class) {
|
|
// @codingStandardsIgnoreStart
|
|
$regex = str_replace('&', $class->getName(), '/\* Laminas_Code_Generator_Php_File-ClassMarker: \{[A-Za-z0-9\\\]+?&\} \*/');
|
|
// @codingStandardsIgnoreEnd
|
|
if (preg_match('#' . $regex . '#m', $output)) {
|
|
$output = preg_replace('#' . $regex . '#', $class->generate(), $output, 1);
|
|
} else {
|
|
if ($namespace) {
|
|
$class->setNamespaceName(null);
|
|
}
|
|
$output .= $class->generate() . self::LINE_FEED;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! empty($body)) {
|
|
// add an extra space between classes and
|
|
if (! empty($classes)) {
|
|
$output .= self::LINE_FEED;
|
|
}
|
|
|
|
$output .= $body;
|
|
}
|
|
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* @return FileGenerator
|
|
* @throws Exception\RuntimeException
|
|
*/
|
|
public function write()
|
|
{
|
|
if ($this->filename == '' || ! is_writable(dirname($this->filename))) {
|
|
throw new Exception\RuntimeException('This code generator object is not writable.');
|
|
}
|
|
file_put_contents($this->filename, $this->generate());
|
|
|
|
return $this;
|
|
}
|
|
}
|