mirror of
https://github.com/standardebooks/web.git
synced 2025-07-12 09:32:24 -04:00
Add Safe PHP functions
This commit is contained in:
parent
04a956886a
commit
58cc098058
260 changed files with 49458 additions and 45 deletions
30
vendor/thecodingmachine/safe/generator/src/ComposerJsonEditor.php
vendored
Normal file
30
vendor/thecodingmachine/safe/generator/src/ComposerJsonEditor.php
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Safe;
|
||||
|
||||
/**
|
||||
* This class will edit the main composer.json file to add the list of files generated from modules.
|
||||
*/
|
||||
class ComposerJsonEditor
|
||||
{
|
||||
/**
|
||||
* @param string[] $modules A list of modules
|
||||
*/
|
||||
public static function editFiles(array $modules): void
|
||||
{
|
||||
$files = \array_map(function (string $module) {
|
||||
return 'generated/'.lcfirst($module).'.php';
|
||||
}, $modules);
|
||||
$files[] = 'lib/special_cases.php';
|
||||
$composerContent = file_get_contents(__DIR__.'/../../composer.json');
|
||||
if ($composerContent === false) {
|
||||
throw new \RuntimeException('Error while loading composer.json file for edition.');
|
||||
}
|
||||
$composerJson = \json_decode($composerContent, true);
|
||||
$composerJson['autoload']['files'] = $files;
|
||||
|
||||
$newContent = json_encode($composerJson, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES);
|
||||
\file_put_contents(__DIR__.'/../../composer.json', $newContent);
|
||||
}
|
||||
}
|
211
vendor/thecodingmachine/safe/generator/src/DocPage.php
vendored
Normal file
211
vendor/thecodingmachine/safe/generator/src/DocPage.php
vendored
Normal file
|
@ -0,0 +1,211 @@
|
|||
<?php
|
||||
|
||||
namespace Safe;
|
||||
|
||||
use function explode;
|
||||
use function strpos;
|
||||
|
||||
class DocPage
|
||||
{
|
||||
/*
|
||||
* @var string
|
||||
*/
|
||||
private $path;
|
||||
|
||||
/*
|
||||
* @return string
|
||||
* @parameter string
|
||||
*/
|
||||
public function __construct(string $_path)
|
||||
{
|
||||
$this->path = $_path;
|
||||
}
|
||||
|
||||
/*
|
||||
* Detect function which didn't return FALSE on error.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function detectFalsyFunction(): bool
|
||||
{
|
||||
$file = file_get_contents($this->path);
|
||||
|
||||
if (preg_match('/&warn\.deprecated\.function-(\d+-\d+-\d+)\.removed-(\d+-\d+-\d+)/', $file, $matches)) {
|
||||
$removedVersion = $matches[2];
|
||||
[$major, $minor] = explode('-', $removedVersion);
|
||||
if ($major < 7 || ($major == 7 && $minor == 0)) {
|
||||
// Ignore function if it was removed before PHP 7.1
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (preg_match('/&warn\.removed\.function-(\d+-\d+-\d+)/', $file, $matches) && isset($matches[2])) {
|
||||
$removedVersion = $matches[2];
|
||||
[$major, $minor] = explode('-', $removedVersion);
|
||||
if ($major < 7 || ($major == 7 && $minor == 0)) {
|
||||
// Ignore function if it was removed before PHP 7.1
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match('/&false;\s+on\s+error/m', $file)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/&false;\s+on\s+failure/m', $file)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/&false;\s+otherwise/m', $file) && !preg_match('/(returns\s+&true;|&true;\s+on\s+success|&true;\s+if)/im', $file)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/may\s+return\s+&false;/m', $file) && !preg_match('/(returns\s+&true;|&true;\s+on\s+success|&true;\s+if)/im', $file)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/&false;\s+if\s+an\s+error\s+occurred/m', $file)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/&return.success;/m', $file)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/&return.nullorfalse;/m', $file)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/&return.falseforfailure;/m', $file)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/&date.datetime.return.modifiedobjectorfalseforfailure;/m', $file)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/ or &false; \\(and generates an error/m', $file)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/&false;\s+if\s+the\s+number\s+of\s+elements\s+for\s+each\s+array\s+isn\'t\s+equal/m', $file)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/If\s+the\s+call\s+fails,\s+it\s+will\s+return\s+&false;/m', $file)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return \SimpleXMLElement[]
|
||||
*/
|
||||
public function getMethodSynopsis(): array
|
||||
{
|
||||
/** @var string[] $cleanedFunctions */
|
||||
$cleanedFunctions = [];
|
||||
|
||||
$file = \file_get_contents($this->path);
|
||||
if (!preg_match_all('/<\/?methodsynopsis[\s\S]*?>[\s\S]*?<\/methodsynopsis>/m', $file, $functions, PREG_SET_ORDER, 0)) {
|
||||
return [];
|
||||
}
|
||||
$functions = $this->arrayFlatten($functions);
|
||||
foreach ($functions as $function) {
|
||||
$cleaningFunction = \str_replace(['&false;', '&true;', '&null;'], ['false', 'true', 'null'], $function);
|
||||
$cleaningFunction = preg_replace('/&(.*);/m', '', $cleaningFunction);
|
||||
if (!\is_string($cleaningFunction)) {
|
||||
throw new \RuntimeException('Error occured in preg_replace');
|
||||
}
|
||||
$cleanedFunctions[] = $cleaningFunction;
|
||||
}
|
||||
$functionObjects = [];
|
||||
foreach ($cleanedFunctions as $cleanedFunction) {
|
||||
$functionObject = \simplexml_load_string($cleanedFunction);
|
||||
if ($functionObject) {
|
||||
$functionObjects[] = $functionObject;
|
||||
}
|
||||
}
|
||||
return $functionObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the XML file, resolving all DTD declared entities.
|
||||
*
|
||||
* @return \SimpleXMLElement
|
||||
*/
|
||||
public function loadAndResolveFile(): \SimpleXMLElement
|
||||
{
|
||||
$content = \file_get_contents($this->path);
|
||||
$strpos = \strpos($content, '?>')+2;
|
||||
if (!\file_exists(__DIR__.'/../doc/entities/generated.ent')) {
|
||||
self::buildEntities();
|
||||
}
|
||||
$path = \realpath(__DIR__.'/../doc/entities/generated.ent');
|
||||
|
||||
|
||||
$content = \substr($content, 0, $strpos)
|
||||
.'<!DOCTYPE refentry SYSTEM "'.$path.'">'
|
||||
.\substr($content, $strpos+1);
|
||||
|
||||
echo 'Loading '.$this->path."\n";
|
||||
$elem = \simplexml_load_string($content, \SimpleXMLElement::class, LIBXML_DTDLOAD | LIBXML_NOENT);
|
||||
if ($elem === false) {
|
||||
throw new \RuntimeException('Invalid XML file for '.$this->path);
|
||||
}
|
||||
$elem->registerXPathNamespace('docbook', 'http://docbook.org/ns/docbook');
|
||||
|
||||
return $elem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the module name in Camelcase.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getModule(): string
|
||||
{
|
||||
return $this->toCamelCase(\basename(\dirname($this->path, 2)));
|
||||
}
|
||||
|
||||
private function toCamelCase(string $str): string
|
||||
{
|
||||
$tokens = preg_split("/[_ ]+/", $str);
|
||||
if ($tokens === false) {
|
||||
throw new \RuntimeException('Unexpected preg_split error'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$str = '';
|
||||
foreach ($tokens as $token) {
|
||||
$str .= ucfirst($token);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $array multidimensional string array
|
||||
* @return string[]
|
||||
*/
|
||||
private function arrayFlatten(array $array): array
|
||||
{
|
||||
$result = array();
|
||||
|
||||
foreach ($array as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$result = array_merge($result, $this->arrayFlatten($value));
|
||||
} else {
|
||||
$result[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function buildEntities(): void
|
||||
{
|
||||
$file1 = \file_get_contents(__DIR__.'/../doc/doc-en/en/language-defs.ent');
|
||||
$file2 = \file_get_contents(__DIR__.'/../doc/doc-en/en/language-snippets.ent');
|
||||
$file3 = \file_get_contents(__DIR__.'/../doc/doc-en/en/extensions.ent');
|
||||
$file4 = \file_get_contents(__DIR__.'/../doc/doc-en/doc-base/entities/global.ent');
|
||||
|
||||
$completeFile = $file1 . self::extractXmlHeader($file2) . self::extractXmlHeader($file3) . $file4;
|
||||
|
||||
\file_put_contents(__DIR__.'/../doc/entities/generated.ent', $completeFile);
|
||||
}
|
||||
|
||||
private static function extractXmlHeader(string $content): string
|
||||
{
|
||||
$strpos = strpos($content, '?>')+2;
|
||||
return substr($content, $strpos);
|
||||
}
|
||||
}
|
11
vendor/thecodingmachine/safe/generator/src/EmptyTypeException.php
vendored
Normal file
11
vendor/thecodingmachine/safe/generator/src/EmptyTypeException.php
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Safe;
|
||||
|
||||
use Exception;
|
||||
|
||||
class EmptyTypeException extends Exception
|
||||
{
|
||||
|
||||
}
|
169
vendor/thecodingmachine/safe/generator/src/FileCreator.php
vendored
Normal file
169
vendor/thecodingmachine/safe/generator/src/FileCreator.php
vendored
Normal file
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
namespace Safe;
|
||||
|
||||
use function array_merge;
|
||||
use Complex\Exception;
|
||||
use function file_exists;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
||||
class FileCreator
|
||||
{
|
||||
/**
|
||||
* This function generate an xls file
|
||||
*
|
||||
* @param string[] $protoFunctions
|
||||
* @param string $path
|
||||
*/
|
||||
public function generateXlsFile(array $protoFunctions, string $path): void
|
||||
{
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$numb = 1;
|
||||
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$sheet->setCellValue('A1', 'Function name');
|
||||
$sheet->setCellValue('B1', 'Status');
|
||||
|
||||
foreach ($protoFunctions as $protoFunction) {
|
||||
if ($protoFunction) {
|
||||
if (strpos($protoFunction, '=') === false && strpos($protoFunction, 'json') === false) {
|
||||
$status = 'classic';
|
||||
} elseif (strpos($protoFunction, 'json')) {
|
||||
$status = 'json';
|
||||
} else {
|
||||
$status = 'opt';
|
||||
}
|
||||
$sheet->setCellValue('A'.$numb, $protoFunction);
|
||||
$sheet->setCellValue('B'.$numb++, $status);
|
||||
}
|
||||
}
|
||||
$writer = new Xlsx($spreadsheet);
|
||||
$writer->save($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function generate an improved php lib function in a php file
|
||||
*
|
||||
* @param Method[] $functions
|
||||
* @param string $path
|
||||
*/
|
||||
public function generatePhpFile(array $functions, string $path): void
|
||||
{
|
||||
$path = rtrim($path, '/').'/';
|
||||
$phpFunctionsByModule = [];
|
||||
foreach ($functions as $function) {
|
||||
$writePhpFunction = new WritePhpFunction($function);
|
||||
$phpFunctionsByModule[$function->getModuleName()][] = $writePhpFunction->getPhpFunctionalFunction();
|
||||
}
|
||||
|
||||
foreach ($phpFunctionsByModule as $module => $phpFunctions) {
|
||||
$lcModule = \lcfirst($module);
|
||||
$stream = \fopen($path.$lcModule.'.php', 'w');
|
||||
if ($stream === false) {
|
||||
throw new \RuntimeException('Unable to write to '.$path);
|
||||
}
|
||||
\fwrite($stream, "<?php\n
|
||||
namespace Safe;
|
||||
|
||||
use Safe\\Exceptions\\".self::toExceptionName($module). ';
|
||||
|
||||
');
|
||||
foreach ($phpFunctions as $phpFunction) {
|
||||
\fwrite($stream, $phpFunction."\n");
|
||||
}
|
||||
\fclose($stream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Method[] $functions
|
||||
* @return string[]
|
||||
*/
|
||||
private function getFunctionsNameList(array $functions): array
|
||||
{
|
||||
$functionNames = array_map(function (Method $function) {
|
||||
return $function->getFunctionName();
|
||||
}, $functions);
|
||||
$specialCases = require __DIR__.'/../config/specialCasesFunctions.php';
|
||||
return array_merge($functionNames, $specialCases);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function generate a PHP file containing the list of functions we can handle.
|
||||
*
|
||||
* @param Method[] $functions
|
||||
* @param string $path
|
||||
*/
|
||||
public function generateFunctionsList(array $functions, string $path): void
|
||||
{
|
||||
$functionNames = $this->getFunctionsNameList($functions);
|
||||
$stream = fopen($path, 'w');
|
||||
if ($stream === false) {
|
||||
throw new \RuntimeException('Unable to write to '.$path);
|
||||
}
|
||||
fwrite($stream, "<?php\n
|
||||
return [\n");
|
||||
foreach ($functionNames as $functionName) {
|
||||
fwrite($stream, ' '.\var_export($functionName, true).",\n");
|
||||
}
|
||||
fwrite($stream, "];\n");
|
||||
fclose($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function generate a rector yml file containing a replacer for all functions
|
||||
*
|
||||
* @param Method[] $functions
|
||||
* @param string $path
|
||||
*/
|
||||
public function generateRectorFile(array $functions, string $path): void
|
||||
{
|
||||
$functionNames = $this->getFunctionsNameList($functions);
|
||||
$stream = fopen($path, 'w');
|
||||
if ($stream === false) {
|
||||
throw new \RuntimeException('Unable to write to '.$path);
|
||||
}
|
||||
fwrite($stream, "# This rector file is replacing all core PHP functions with the equivalent \"safe\" functions
|
||||
services:
|
||||
Rector\Rector\Function_\FunctionReplaceRector:
|
||||
\$oldFunctionToNewFunction:
|
||||
");
|
||||
foreach ($functionNames as $functionName) {
|
||||
fwrite($stream, ' '.$functionName.": 'Safe\\".$functionName."'\n");
|
||||
}
|
||||
fclose($stream);
|
||||
}
|
||||
|
||||
|
||||
public function createExceptionFile(string $moduleName): void
|
||||
{
|
||||
$exceptionName = self::toExceptionName($moduleName);
|
||||
if (!file_exists(__DIR__.'/../../lib/Exceptions/'.$exceptionName.'.php')) {
|
||||
\file_put_contents(
|
||||
__DIR__.'/../../generated/Exceptions/'.$exceptionName.'.php',
|
||||
<<<EOF
|
||||
<?php
|
||||
namespace Safe\Exceptions;
|
||||
|
||||
class {$exceptionName} extends AbstractSafeException
|
||||
{
|
||||
}
|
||||
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the name of the exception class
|
||||
*
|
||||
* @param string $moduleName
|
||||
* @return string
|
||||
*/
|
||||
public static function toExceptionName(string $moduleName): string
|
||||
{
|
||||
return str_replace('-', '', \ucfirst($moduleName)).'Exception';
|
||||
}
|
||||
}
|
107
vendor/thecodingmachine/safe/generator/src/GenerateCommand.php
vendored
Normal file
107
vendor/thecodingmachine/safe/generator/src/GenerateCommand.php
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Safe;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class GenerateCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('generate')
|
||||
->setDescription('Generates the PHP file with all functions.')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
|
||||
$this->rmGenerated();
|
||||
// Let's build the DTD necessary to load the XML files.
|
||||
DocPage::buildEntities();
|
||||
$scanner = new Scanner(__DIR__ . '/../doc/doc-en/en/reference/');
|
||||
|
||||
$paths = $scanner->getFunctionsPaths();
|
||||
|
||||
[
|
||||
'functions' => $functions,
|
||||
'overloadedFunctions' => $overloadedFunctions
|
||||
] = $scanner->getMethods($paths);
|
||||
|
||||
$output->writeln('These functions have been ignored and must be dealt with manually: '.\implode(', ', $overloadedFunctions));
|
||||
|
||||
$fileCreator = new FileCreator();
|
||||
//$fileCreator->generateXlsFile($protoFunctions, __DIR__ . '/../generated/lib.xls');
|
||||
$fileCreator->generatePhpFile($functions, __DIR__ . '/../../generated/');
|
||||
$fileCreator->generateFunctionsList($functions, __DIR__ . '/../../generated/functionsList.php');
|
||||
$fileCreator->generateRectorFile($functions, __DIR__ . '/../../rector-migrate.yml');
|
||||
|
||||
|
||||
$modules = [];
|
||||
foreach ($functions as $function) {
|
||||
$modules[$function->getModuleName()] = $function->getModuleName();
|
||||
}
|
||||
|
||||
foreach ($modules as $moduleName => $foo) {
|
||||
$fileCreator->createExceptionFile($moduleName);
|
||||
}
|
||||
|
||||
$this->runCsFix($output);
|
||||
|
||||
// Let's require the generated file to check there is no error.
|
||||
$files = \glob(__DIR__.'/../../generated/*.php');
|
||||
|
||||
foreach ($files as $file) {
|
||||
require($file);
|
||||
}
|
||||
|
||||
$files = \glob(__DIR__.'/../../generated/Exceptions/*.php');
|
||||
|
||||
require_once __DIR__.'/../../lib/Exceptions/SafeExceptionInterface.php';
|
||||
require_once __DIR__.'/../../lib/Exceptions/AbstractSafeException.php';
|
||||
foreach ($files as $file) {
|
||||
require($file);
|
||||
}
|
||||
|
||||
// Finally, let's edit the composer.json file
|
||||
$output->writeln('Editing composer.json');
|
||||
ComposerJsonEditor::editFiles(\array_values($modules));
|
||||
}
|
||||
|
||||
private function rmGenerated(): void
|
||||
{
|
||||
$exceptions = \glob(__DIR__.'/../../generated/Exceptions/*.php');
|
||||
|
||||
foreach ($exceptions as $exception) {
|
||||
\unlink($exception);
|
||||
}
|
||||
|
||||
$files = \glob(__DIR__.'/../../generated/*.php');
|
||||
|
||||
foreach ($files as $file) {
|
||||
\unlink($file);
|
||||
}
|
||||
|
||||
if (\file_exists(__DIR__.'/../doc/entities/generated.ent')) {
|
||||
\unlink(__DIR__.'/../doc/entities/generated.ent');
|
||||
}
|
||||
}
|
||||
|
||||
private function runCsFix(OutputInterface $output): void
|
||||
{
|
||||
$process = new Process('vendor/bin/phpcbf', __DIR__.'/../..');
|
||||
$process->setTimeout(600);
|
||||
$process->run(function ($type, $buffer) use ($output) {
|
||||
if (Process::ERR === $type) {
|
||||
echo $output->write('<error>'.$buffer.'</error>');
|
||||
} else {
|
||||
echo $output->write($buffer);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
233
vendor/thecodingmachine/safe/generator/src/Method.php
vendored
Normal file
233
vendor/thecodingmachine/safe/generator/src/Method.php
vendored
Normal file
|
@ -0,0 +1,233 @@
|
|||
<?php
|
||||
|
||||
namespace Safe;
|
||||
|
||||
use Safe\PhpStanFunctions\PhpStanFunction;
|
||||
use Safe\PhpStanFunctions\PhpStanFunctionMapReader;
|
||||
|
||||
class Method
|
||||
{
|
||||
/**
|
||||
* @var \SimpleXMLElement
|
||||
*/
|
||||
private $functionObject;
|
||||
/**
|
||||
* @var \SimpleXMLElement
|
||||
*/
|
||||
private $rootEntity;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $moduleName;
|
||||
/**
|
||||
* @var Parameter[]|null
|
||||
*/
|
||||
private $params = null;
|
||||
/**
|
||||
* @var PhpStanFunctionMapReader
|
||||
*/
|
||||
private $phpStanFunctionMapReader;
|
||||
|
||||
public function __construct(\SimpleXMLElement $_functionObject, \SimpleXMLElement $rootEntity, string $moduleName, PhpStanFunctionMapReader $phpStanFunctionMapReader)
|
||||
{
|
||||
$this->functionObject = $_functionObject;
|
||||
$this->rootEntity = $rootEntity;
|
||||
$this->moduleName = $moduleName;
|
||||
$this->phpStanFunctionMapReader = $phpStanFunctionMapReader;
|
||||
}
|
||||
|
||||
public function getFunctionName(): string
|
||||
{
|
||||
return $this->functionObject->methodname->__toString();
|
||||
}
|
||||
|
||||
public function getReturnType(): string
|
||||
{
|
||||
// If the function returns a boolean, since false is for error, true is for success.
|
||||
// Let's replace this with a "void".
|
||||
$type = $this->functionObject->type->__toString();
|
||||
if ($type === 'bool') {
|
||||
return 'void';
|
||||
}
|
||||
// Some types are completely weird. For instance, oci_new_collection returns a "OCI-Collection" (with a dash, yup)
|
||||
if (\strpos($type, '-') !== false) {
|
||||
return 'mixed';
|
||||
}
|
||||
|
||||
return Type::toRootNamespace($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Parameter[]
|
||||
*/
|
||||
public function getParams(): array
|
||||
{
|
||||
if ($this->params === null) {
|
||||
if (!isset($this->functionObject->methodparam)) {
|
||||
return [];
|
||||
}
|
||||
$phpStanFunction = $this->getPhpStanData();
|
||||
$params = [];
|
||||
$i=1;
|
||||
foreach ($this->functionObject->methodparam as $param) {
|
||||
$notes = $this->stripReturnFalseText($this->getStringForXPath("(//docbook:refsect1[@role='parameters']//docbook:varlistentry)[$i]//docbook:note//docbook:para"));
|
||||
$i++;
|
||||
|
||||
if (preg_match('/This parameter has been removed in PHP (\d+\.\d+\.\d+)/', $notes, $matches)) {
|
||||
$removedVersion = $matches[1];
|
||||
[$major, $minor] = explode('.', $removedVersion);
|
||||
if ($major < 7 || ($major == 7 && $minor == 0)) {
|
||||
// Ignore parameter if it was removed before PHP 7.1
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$params[] = new Parameter($param, $phpStanFunction);
|
||||
}
|
||||
$this->params = $params;
|
||||
}
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
public function getPhpDoc(): string
|
||||
{
|
||||
$str = "/**\n".
|
||||
implode("\n", array_map(function (string $line) {
|
||||
return ' * '.ltrim($line);
|
||||
}, \explode("\n", \strip_tags($this->getDocBlock()))))
|
||||
."\n */\n";
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
private function getDocBlock(): string
|
||||
{
|
||||
$str = $this->stripReturnFalseText($this->getStringForXPath("//docbook:refsect1[@role='description']/docbook:para"));
|
||||
$str .= "\n\n";
|
||||
|
||||
$i=1;
|
||||
foreach ($this->getParams() as $parameter) {
|
||||
$str .= '@param '.$parameter->getBestType().' $'.$parameter->getParameter().' ';
|
||||
$str .= $this->getStringForXPath("(//docbook:refsect1[@role='parameters']//docbook:varlistentry)[$i]//docbook:para")."\n";
|
||||
$i++;
|
||||
}
|
||||
|
||||
$bestReturnType = $this->getBestReturnType();
|
||||
if ($bestReturnType !== 'void') {
|
||||
$str .= '@return '.$bestReturnType. ' ' .$this->getReturnDoc()."\n";
|
||||
}
|
||||
|
||||
$str .= '@throws '.FileCreator::toExceptionName($this->getModuleName()). "\n";
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
private function getReturnDoc(): string
|
||||
{
|
||||
$returnDoc = $this->getStringForXPath("//docbook:refsect1[@role='returnvalues']/docbook:para");
|
||||
return $this->stripReturnFalseText($returnDoc);
|
||||
}
|
||||
|
||||
private function stripReturnFalseText(string $string): string
|
||||
{
|
||||
$string = \strip_tags($string);
|
||||
$string = $this->removeString($string, 'or FALSE on failure');
|
||||
$string = $this->removeString($string, 'may return FALSE');
|
||||
$string = $this->removeString($string, 'and FALSE on failure');
|
||||
$string = $this->removeString($string, 'on success, or FALSE otherwise');
|
||||
$string = $this->removeString($string, 'or FALSE on error');
|
||||
$string = $this->removeString($string, 'or FALSE if an error occurred');
|
||||
$string = $this->removeString($string, 'the function will return TRUE, or FALSE otherwise');
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a string, even if the string is split on multiple lines.
|
||||
* @param string $string
|
||||
* @param string $search
|
||||
* @return string
|
||||
*/
|
||||
private function removeString(string $string, string $search): string
|
||||
{
|
||||
$search = str_replace(' ', '\s+', $search);
|
||||
$result = preg_replace('/[\s\,]*'.$search.'/m', '', $string);
|
||||
if ($result === null) {
|
||||
throw new \RuntimeException('An error occurred while calling preg_replace');
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getStringForXPath(string $xpath): string
|
||||
{
|
||||
$paragraphs = $this->rootEntity->xpath($xpath);
|
||||
if ($paragraphs === false) {
|
||||
throw new \RuntimeException('Error while performing Xpath request.');
|
||||
}
|
||||
$str = '';
|
||||
foreach ($paragraphs as $paragraph) {
|
||||
$str .= $this->getInnerXml($paragraph)."\n\n";
|
||||
}
|
||||
return trim($str);
|
||||
}
|
||||
|
||||
private function getBestReturnType(): ?string
|
||||
{
|
||||
$phpStanFunction = $this->getPhpStanData();
|
||||
// Get the type from PhpStan database first, then from the php doc.
|
||||
if ($phpStanFunction !== null) {
|
||||
return Type::toRootNamespace($phpStanFunction->getReturnType());
|
||||
} else {
|
||||
return Type::toRootNamespace($this->getReturnType());
|
||||
}
|
||||
}
|
||||
|
||||
private function getPhpStanData(): ?PhpStanFunction
|
||||
{
|
||||
$functionName = $this->getFunctionName();
|
||||
if (!$this->phpStanFunctionMapReader->hasFunction($functionName)) {
|
||||
return null;
|
||||
}
|
||||
return $this->phpStanFunctionMapReader->getFunction($functionName);
|
||||
}
|
||||
|
||||
private function getInnerXml(\SimpleXMLElement $SimpleXMLElement): string
|
||||
{
|
||||
$element_name = $SimpleXMLElement->getName();
|
||||
$inner_xml = $SimpleXMLElement->asXML();
|
||||
if ($inner_xml === false) {
|
||||
throw new \RuntimeException('Unable to serialize to XML');
|
||||
}
|
||||
$inner_xml = str_replace(['<'.$element_name.'>', '</'.$element_name.'>'], '', $inner_xml);
|
||||
$inner_xml = trim($inner_xml);
|
||||
return $inner_xml;
|
||||
}
|
||||
|
||||
public function getModuleName(): string
|
||||
{
|
||||
return $this->moduleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The function is overloaded if at least one parameter is optional with no default value and this parameter is not by reference.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isOverloaded(): bool
|
||||
{
|
||||
foreach ($this->getParams() as $parameter) {
|
||||
if ($parameter->isOptionalWithNoDefault() && !$parameter->isByReference()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function cloneAndRemoveAParameter(): Method
|
||||
{
|
||||
$new = clone $this;
|
||||
$params = $this->getParams();
|
||||
\array_pop($params);
|
||||
$new->params = $params;
|
||||
return $new;
|
||||
}
|
||||
}
|
160
vendor/thecodingmachine/safe/generator/src/Parameter.php
vendored
Normal file
160
vendor/thecodingmachine/safe/generator/src/Parameter.php
vendored
Normal file
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
namespace Safe;
|
||||
|
||||
use Safe\PhpStanFunctions\PhpStanFunction;
|
||||
|
||||
class Parameter
|
||||
{
|
||||
/**
|
||||
* @var \SimpleXMLElement
|
||||
*/
|
||||
private $parameter;
|
||||
/**
|
||||
* @var PhpStanFunction|null
|
||||
*/
|
||||
private $phpStanFunction;
|
||||
|
||||
public function __construct(\SimpleXMLElement $parameter, ?PhpStanFunction $phpStanFunction)
|
||||
{
|
||||
$this->parameter = $parameter;
|
||||
$this->phpStanFunction = $phpStanFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type as declared in the doc.
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
$type = $this->parameter->type->__toString();
|
||||
$strType = Type::toRootNamespace($type);
|
||||
if ($strType !== 'mixed' && $strType !== 'resource' && $this->phpStanFunction !== null) {
|
||||
$phpStanParameter = $this->phpStanFunction->getParameter($this->getParameter());
|
||||
if ($phpStanParameter) {
|
||||
// Let's make the parameter nullable if it is by reference and is used only for writing.
|
||||
if ($phpStanParameter->isWriteOnly()) {
|
||||
$strType = '?'.$strType;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $strType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type as declared in the doc.
|
||||
* @return string
|
||||
*/
|
||||
public function getBestType(): string
|
||||
{
|
||||
// Get the type from PhpStan database first, then from the php doc.
|
||||
if ($this->phpStanFunction !== null) {
|
||||
$phpStanParameter = $this->phpStanFunction->getParameter($this->getParameter());
|
||||
if ($phpStanParameter) {
|
||||
try {
|
||||
return $phpStanParameter->getType();
|
||||
} catch (EmptyTypeException $e) {
|
||||
// If the type is empty in PHPStan, let's fallback to documentation.
|
||||
return $this->getType();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->getType();
|
||||
}
|
||||
|
||||
/*
|
||||
* @return string
|
||||
*/
|
||||
public function getParameter(): string
|
||||
{
|
||||
if ($this->isVariadic()) {
|
||||
return 'params';
|
||||
}
|
||||
// The db2_bind_param method has parameters with a dash in it... yep... (patch submitted)
|
||||
return \str_replace('-', '_', $this->parameter->parameter->__toString());
|
||||
}
|
||||
|
||||
public function isByReference(): bool
|
||||
{
|
||||
return ((string)$this->parameter->parameter['role']) === 'reference';
|
||||
}
|
||||
|
||||
/**
|
||||
* Some parameters can be optional with no default value. In this case, the function is "overloaded" (which is not
|
||||
* possible in user-land but possible in core...)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isOptionalWithNoDefault(): bool
|
||||
{
|
||||
if (((string)$this->parameter['choice']) !== 'opt') {
|
||||
return false;
|
||||
}
|
||||
if (!$this->hasDefaultValue()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$initializer = $this->getInitializer();
|
||||
// Some default value have weird values. For instance, first parameter of "mb_internal_encoding" has default value "mb_internal_encoding()"
|
||||
if ($initializer !== 'array()' && strpos($initializer, '(') !== false) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isVariadic(): bool
|
||||
{
|
||||
return $this->parameter->parameter->__toString() === '...';
|
||||
}
|
||||
|
||||
public function isNullable(): bool
|
||||
{
|
||||
if ($this->phpStanFunction !== null) {
|
||||
$phpStanParameter = $this->phpStanFunction->getParameter($this->getParameter());
|
||||
if ($phpStanParameter) {
|
||||
return $phpStanParameter->isNullable();
|
||||
}
|
||||
}
|
||||
return $this->hasDefaultValue() && $this->getDefaultValue() === 'null';
|
||||
}
|
||||
|
||||
/*
|
||||
* @return string
|
||||
*/
|
||||
public function getInitializer(): string
|
||||
{
|
||||
return \str_replace(['<constant>', '</constant>'], '', $this->getInnerXml($this->parameter->initializer));
|
||||
}
|
||||
|
||||
public function hasDefaultValue(): bool
|
||||
{
|
||||
return isset($this->parameter->initializer);
|
||||
}
|
||||
|
||||
public function getDefaultValue(): ?string
|
||||
{
|
||||
if (!$this->hasDefaultValue()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$initializer = $this->getInitializer();
|
||||
|
||||
// Some default value have weird values. For instance, first parameter of "mb_internal_encoding" has default value "mb_internal_encoding()"
|
||||
if (strpos($initializer, '(') !== false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $initializer;
|
||||
}
|
||||
|
||||
private function getInnerXml(\SimpleXMLElement $SimpleXMLElement): string
|
||||
{
|
||||
$element_name = $SimpleXMLElement->getName();
|
||||
$inner_xml = $SimpleXMLElement->asXML();
|
||||
if ($inner_xml === false) {
|
||||
throw new \RuntimeException('Unable to serialize to XML');
|
||||
}
|
||||
$inner_xml = str_replace(['<'.$element_name.'>', '</'.$element_name.'>', '<'.$element_name.'/>'], '', $inner_xml);
|
||||
$inner_xml = trim($inner_xml);
|
||||
return $inner_xml;
|
||||
}
|
||||
}
|
53
vendor/thecodingmachine/safe/generator/src/PhpStanFunctions/PhpStanFunction.php
vendored
Normal file
53
vendor/thecodingmachine/safe/generator/src/PhpStanFunctions/PhpStanFunction.php
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Safe\PhpStanFunctions;
|
||||
|
||||
class PhpStanFunction
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $returnType;
|
||||
|
||||
/**
|
||||
* @var PhpStanParameter[]
|
||||
*/
|
||||
private $parameters = [];
|
||||
|
||||
/**
|
||||
* @param mixed[] $signature
|
||||
*/
|
||||
public function __construct(array $signature)
|
||||
{
|
||||
$this->returnType = \array_shift($signature);
|
||||
foreach ($signature as $name => $type) {
|
||||
$param = new PhpStanParameter($name, $type);
|
||||
$this->parameters[$param->getName()] = $param;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getReturnType(): string
|
||||
{
|
||||
if ($this->returnType === 'bool') {
|
||||
$this->returnType = 'void';
|
||||
}
|
||||
return \str_replace(['|bool', '|false'], '', $this->returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,PhpStanParameter>
|
||||
*/
|
||||
public function getParameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
public function getParameter(string $name): ?PhpStanParameter
|
||||
{
|
||||
return $this->parameters[$name] ?? null;
|
||||
}
|
||||
}
|
27
vendor/thecodingmachine/safe/generator/src/PhpStanFunctions/PhpStanFunctionMapReader.php
vendored
Normal file
27
vendor/thecodingmachine/safe/generator/src/PhpStanFunctions/PhpStanFunctionMapReader.php
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Safe\PhpStanFunctions;
|
||||
|
||||
class PhpStanFunctionMapReader
|
||||
{
|
||||
/**
|
||||
* @var array<string, array>
|
||||
*/
|
||||
private $functionMap;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->functionMap = require __DIR__.'/../../vendor/phpstan/phpstan/src/Reflection/SignatureMap/functionMap.php';
|
||||
}
|
||||
|
||||
public function hasFunction(string $functionName): bool
|
||||
{
|
||||
return isset($this->functionMap[$functionName]);
|
||||
}
|
||||
|
||||
public function getFunction(string $functionName): PhpStanFunction
|
||||
{
|
||||
return new PhpStanFunction($this->functionMap[$functionName]);
|
||||
}
|
||||
}
|
124
vendor/thecodingmachine/safe/generator/src/PhpStanFunctions/PhpStanParameter.php
vendored
Normal file
124
vendor/thecodingmachine/safe/generator/src/PhpStanFunctions/PhpStanParameter.php
vendored
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Safe\PhpStanFunctions;
|
||||
|
||||
use Safe\Type;
|
||||
|
||||
class PhpStanParameter
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $optional = false;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $variadic = false;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $byReference = false;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $nullable = false;
|
||||
/**
|
||||
* Whether the parameter is "write only" (applies only to "by reference" parameters)
|
||||
* @var bool
|
||||
*/
|
||||
private $writeOnly = false;
|
||||
|
||||
public function __construct(string $name, string $type)
|
||||
{
|
||||
if (\strpos($name, '=') !== false) {
|
||||
$this->optional = true;
|
||||
}
|
||||
if (\strpos($name, '...') !== false) {
|
||||
$this->variadic = true;
|
||||
}
|
||||
if (\strpos($name, '&') !== false) {
|
||||
$this->byReference = true;
|
||||
}
|
||||
if (\strpos($name, '&w_') !== false) {
|
||||
$this->writeOnly = true;
|
||||
}
|
||||
$name = \str_replace(['&rw_', '&w_'], '', $name);
|
||||
$name = trim($name, '=.&');
|
||||
|
||||
$this->name = $name;
|
||||
|
||||
if (\strpos($type, '?') !== false) {
|
||||
$type = \str_replace('?', '', $type).'|null';
|
||||
$this->nullable = true;
|
||||
}
|
||||
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Type::toRootNamespace($this->type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isOptional(): bool
|
||||
{
|
||||
return $this->optional;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isVariadic(): bool
|
||||
{
|
||||
return $this->variadic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isByReference(): bool
|
||||
{
|
||||
return $this->byReference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the parameter is "write only" (applies only to "by reference" parameters)
|
||||
* @return bool
|
||||
*/
|
||||
public function isWriteOnly(): bool
|
||||
{
|
||||
return $this->writeOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isNullable(): bool
|
||||
{
|
||||
return $this->nullable;
|
||||
}
|
||||
}
|
38
vendor/thecodingmachine/safe/generator/src/ScanObjectsCommand.php
vendored
Normal file
38
vendor/thecodingmachine/safe/generator/src/ScanObjectsCommand.php
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Safe;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ScanObjectsCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('scan-objects')
|
||||
->setDescription('Displays all methods of all objects not handled yet by Safe.')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$scanner = new Scanner(__DIR__ . '/../doc/doc-en/en/reference/');
|
||||
|
||||
$paths = $scanner->getMethodsPaths();
|
||||
|
||||
[
|
||||
'functions' => $functions,
|
||||
'overloadedFunctions' => $overloadedFunctions
|
||||
] = $scanner->getMethods($paths);
|
||||
|
||||
foreach ($functions as $function) {
|
||||
$name = $function->getFunctionName();
|
||||
$output->writeln('Found method '.$name);
|
||||
}
|
||||
|
||||
$output->writeln('These methods are overloaded: '.\implode(', ', $overloadedFunctions));
|
||||
}
|
||||
}
|
124
vendor/thecodingmachine/safe/generator/src/Scanner.php
vendored
Normal file
124
vendor/thecodingmachine/safe/generator/src/Scanner.php
vendored
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace Safe;
|
||||
|
||||
use function array_merge;
|
||||
use function iterator_to_array;
|
||||
use Safe\PhpStanFunctions\PhpStanFunctionMapReader;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use SplFileInfo;
|
||||
|
||||
class Scanner
|
||||
{
|
||||
/*
|
||||
* @var string
|
||||
*/
|
||||
private $path;
|
||||
|
||||
public function __construct(string $path)
|
||||
{
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, SplFileInfo>
|
||||
*/
|
||||
public function getFunctionsPaths(): array
|
||||
{
|
||||
$finder = new Finder();
|
||||
$finder->in($this->path.'*/functions/')->name('*.xml')->sortByName();
|
||||
|
||||
return iterator_to_array($finder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, SplFileInfo>
|
||||
*/
|
||||
public function getMethodsPaths(): array
|
||||
{
|
||||
$finder = new Finder();
|
||||
$finder->in($this->path)->notPath('functions')->name('*.xml')->sortByName();
|
||||
|
||||
return iterator_to_array($finder);
|
||||
}
|
||||
|
||||
private $ignoredFunctions;
|
||||
|
||||
/**
|
||||
* Returns the list of functions that must be ignored.
|
||||
* @return string[]
|
||||
*/
|
||||
private function getIgnoredFunctions(): array
|
||||
{
|
||||
if ($this->ignoredFunctions === null) {
|
||||
$ignoredFunctions = require __DIR__.'/../config/ignoredFunctions.php';
|
||||
$specialCaseFunctions = require __DIR__.'/../config/specialCasesFunctions.php';
|
||||
|
||||
$this->ignoredFunctions = array_merge($ignoredFunctions, $specialCaseFunctions);
|
||||
}
|
||||
return $this->ignoredFunctions;
|
||||
}
|
||||
|
||||
private $ignoredModules;
|
||||
|
||||
/**
|
||||
* Returns the list of modules that must be ignored.
|
||||
* @return string[]
|
||||
*/
|
||||
private function getIgnoredModules(): array
|
||||
{
|
||||
if ($this->ignoredModules === null) {
|
||||
$this->ignoredModules = require __DIR__.'/../config/ignoredModules.php';
|
||||
}
|
||||
return $this->ignoredModules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SplFileInfo[] $paths
|
||||
* @return mixed[] Structure: ['functions'=>Method[], 'overloadedFunctions'=>string[]]
|
||||
*/
|
||||
public function getMethods(array $paths): array
|
||||
{
|
||||
$functions = [];
|
||||
$overloadedFunctions = [];
|
||||
|
||||
$phpStanFunctionMapReader = new PhpStanFunctionMapReader();
|
||||
$ignoredFunctions = $this->getIgnoredFunctions();
|
||||
$ignoredFunctions = \array_combine($ignoredFunctions, $ignoredFunctions);
|
||||
$ignoredModules = $this->getIgnoredModules();
|
||||
$ignoredModules = \array_combine($ignoredModules, $ignoredModules);
|
||||
foreach ($paths as $path) {
|
||||
$module = \basename(\dirname($path, 2));
|
||||
if (isset($ignoredModules[$module])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$docPage = new DocPage($path);
|
||||
if ($docPage->detectFalsyFunction()) {
|
||||
$functionObjects = $docPage->getMethodSynopsis();
|
||||
if (count($functionObjects) > 1) {
|
||||
$overloadedFunctions = array_merge($overloadedFunctions, \array_map(function ($functionObject) {
|
||||
return $functionObject->methodname->__toString();
|
||||
}, $functionObjects));
|
||||
$overloadedFunctions = \array_filter($overloadedFunctions, function (string $functionName) use ($ignoredFunctions) {
|
||||
return !isset($ignoredFunctions[$functionName]);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
$rootEntity = $docPage->loadAndResolveFile();
|
||||
foreach ($functionObjects as $functionObject) {
|
||||
$function = new Method($functionObject, $rootEntity, $docPage->getModule(), $phpStanFunctionMapReader);
|
||||
if (isset($ignoredFunctions[$function->getFunctionName()])) {
|
||||
continue;
|
||||
}
|
||||
$functions[] = $function;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'functions' => $functions,
|
||||
'overloadedFunctions' => \array_unique($overloadedFunctions)
|
||||
];
|
||||
}
|
||||
}
|
42
vendor/thecodingmachine/safe/generator/src/Type.php
vendored
Normal file
42
vendor/thecodingmachine/safe/generator/src/Type.php
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Safe;
|
||||
|
||||
class Type
|
||||
{
|
||||
/**
|
||||
* Returns true if the type passed in parameter is a class, false if it is scalar or resource
|
||||
*
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
private static function isClass(string $type): bool
|
||||
{
|
||||
if ($type === '') {
|
||||
throw new EmptyTypeException('Empty type passed');
|
||||
}
|
||||
if ($type === 'stdClass') {
|
||||
return true;
|
||||
}
|
||||
// Classes start with uppercase letters. Otherwise, it's most likely a scalar.
|
||||
if ($type[0] === strtoupper($type[0])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put classes in the root namespace
|
||||
*
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
public static function toRootNamespace(string $type): string
|
||||
{
|
||||
if (self::isClass($type)) {
|
||||
return '\\'.$type;
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
}
|
190
vendor/thecodingmachine/safe/generator/src/WritePhpFunction.php
vendored
Normal file
190
vendor/thecodingmachine/safe/generator/src/WritePhpFunction.php
vendored
Normal file
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
namespace Safe;
|
||||
|
||||
class WritePhpFunction
|
||||
{
|
||||
/**
|
||||
* @var Method
|
||||
*/
|
||||
private $method;
|
||||
|
||||
public function __construct(Method $method)
|
||||
{
|
||||
$this->method = $method;
|
||||
}
|
||||
|
||||
/*
|
||||
* @return string
|
||||
*/
|
||||
public function getPhpPrototypeFunction(): string
|
||||
{
|
||||
if ($this->method->getFunctionName()) {
|
||||
return 'function '.$this->method->getFunctionName().'('.$this->displayParamsWithType($this->method->getParams()).')'.': '.$this->method->getReturnType().'{}';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/*
|
||||
* return string
|
||||
*/
|
||||
public function getPhpFunctionalFunction(): string
|
||||
{
|
||||
if ($this->getPhpPrototypeFunction()) {
|
||||
return $this->writePhpFunction();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/*
|
||||
* return string
|
||||
*/
|
||||
private function writePhpFunction(): string
|
||||
{
|
||||
$phpFunction = $this->method->getPhpDoc();
|
||||
if ($this->method->getReturnType() !== 'mixed' && $this->method->getReturnType() !== 'resource') {
|
||||
$returnType = ': ' . $this->method->getReturnType();
|
||||
} else {
|
||||
$returnType = '';
|
||||
}
|
||||
$returnStatement = '';
|
||||
if ($this->method->getReturnType() !== 'void') {
|
||||
$returnStatement = " return \$result;\n";
|
||||
}
|
||||
$moduleName = $this->method->getModuleName();
|
||||
|
||||
$phpFunction .= "function {$this->method->getFunctionName()}({$this->displayParamsWithType($this->method->getParams())}){$returnType}
|
||||
{
|
||||
error_clear_last();
|
||||
";
|
||||
|
||||
if (!$this->method->isOverloaded()) {
|
||||
$phpFunction .= ' $result = '.$this->printFunctionCall($this->method);
|
||||
} else {
|
||||
$method = $this->method;
|
||||
$inElse = false;
|
||||
do {
|
||||
$lastParameter = $method->getParams()[count($method->getParams())-1];
|
||||
if ($inElse) {
|
||||
$phpFunction .= ' else';
|
||||
} else {
|
||||
$phpFunction .= ' ';
|
||||
}
|
||||
if ($lastParameter->isVariadic()) {
|
||||
$defaultValueToString = '[]';
|
||||
} else {
|
||||
$defaultValue = $lastParameter->getDefaultValue();
|
||||
$defaultValueToString = $this->defaultValueToString($defaultValue);
|
||||
}
|
||||
$phpFunction .= 'if ($'.$lastParameter->getParameter().' !== '.$defaultValueToString.') {'."\n";
|
||||
$phpFunction .= ' $result = '.$this->printFunctionCall($method)."\n";
|
||||
$phpFunction .= ' }';
|
||||
$inElse = true;
|
||||
$method = $method->cloneAndRemoveAParameter();
|
||||
if (!$method->isOverloaded()) {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
$phpFunction .= 'else {'."\n";
|
||||
$phpFunction .= ' $result = '.$this->printFunctionCall($method)."\n";
|
||||
$phpFunction .= ' }';
|
||||
}
|
||||
|
||||
$phpFunction .= $this->generateExceptionCode($moduleName, $this->method).$returnStatement. '}
|
||||
|
||||
';
|
||||
|
||||
return $phpFunction;
|
||||
}
|
||||
|
||||
private function generateExceptionCode(string $moduleName, Method $method) : string
|
||||
{
|
||||
// Special case for CURL: we need the first argument of the method if this is a resource.
|
||||
if ($moduleName === 'Curl') {
|
||||
$params = $method->getParams();
|
||||
if (\count($params) > 0 && $params[0]->getParameter() === 'ch') {
|
||||
return "
|
||||
if (\$result === false) {
|
||||
throw CurlException::createFromCurlResource(\$ch);
|
||||
}
|
||||
";
|
||||
}
|
||||
}
|
||||
|
||||
$exceptionName = FileCreator::toExceptionName($moduleName);
|
||||
return "
|
||||
if (\$result === false) {
|
||||
throw {$exceptionName}::createFromPhpError();
|
||||
}
|
||||
";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Parameter[] $params
|
||||
* @return string
|
||||
*/
|
||||
private function displayParamsWithType(array $params): string
|
||||
{
|
||||
$paramsAsString = [];
|
||||
$optDetected = false;
|
||||
|
||||
foreach ($params as $param) {
|
||||
$paramAsString = '';
|
||||
if ($param->getType() !== 'mixed' && $param->getType() !== 'resource') {
|
||||
if ($param->isNullable()) {
|
||||
$paramAsString .= '?';
|
||||
}
|
||||
$paramAsString .= $param->getType().' ';
|
||||
}
|
||||
|
||||
$paramName = $param->getParameter();
|
||||
if ($param->isVariadic()) {
|
||||
$paramAsString .= ' ...$'.$paramName;
|
||||
} else {
|
||||
if ($param->isByReference()) {
|
||||
$paramAsString .= '&';
|
||||
}
|
||||
$paramAsString .= '$'.$paramName;
|
||||
}
|
||||
|
||||
|
||||
if ($param->hasDefaultValue() || $param->isOptionalWithNoDefault()) {
|
||||
$optDetected = true;
|
||||
}
|
||||
$defaultValue = $param->getDefaultValue();
|
||||
if ($defaultValue !== null) {
|
||||
$paramAsString .= ' = '.$this->defaultValueToString($defaultValue);
|
||||
} elseif ($optDetected && !$param->isVariadic()) {
|
||||
$paramAsString .= ' = null';
|
||||
}
|
||||
$paramsAsString[] = $paramAsString;
|
||||
}
|
||||
|
||||
return implode(', ', $paramsAsString);
|
||||
}
|
||||
|
||||
private function printFunctionCall(Method $function): string
|
||||
{
|
||||
$functionCall = '\\'.$function->getFunctionName().'(';
|
||||
$functionCall .= implode(', ', \array_map(function (Parameter $parameter) {
|
||||
$str = '';
|
||||
if ($parameter->isVariadic()) {
|
||||
$str = '...';
|
||||
}
|
||||
return $str.'$'.$parameter->getParameter();
|
||||
}, $function->getParams()));
|
||||
$functionCall .= ');';
|
||||
return $functionCall;
|
||||
}
|
||||
|
||||
private function defaultValueToString(?string $defaultValue): string
|
||||
{
|
||||
if ($defaultValue === null) {
|
||||
return 'null';
|
||||
}
|
||||
if ($defaultValue === '') {
|
||||
return "''";
|
||||
}
|
||||
return $defaultValue;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue