This commit is contained in:
2022-10-23 01:39:27 +02:00
parent 8c17aab483
commit 1929b84685
4130 changed files with 479334 additions and 0 deletions

738
instafeed/vendor/bin/lazydoctor vendored Executable file
View File

@@ -0,0 +1,738 @@
#!/usr/bin/env php
<?php
/*
* Copyright 2017 The LazyJsonMapper Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* The Lazy Doctor.
*
* This tool automatically builds up-to-date class documentation for all classes
* that derive from LazyJsonMapper, and documents their virtual properties and
* functions by adding "@property" and "@method" declarations to their class
* PHPdoc blocks. That documentation is necessary for things like code analysis
* tools and IDE autocomplete for all of your virtual properties/functions!
*
* LazyDoctor also performs class diagnostics: It compiles the class property
* maps of _all_ of your LazyJsonMapper-based classes, which means that you can
* be 100% sure that all of your maps are valid if this tool runs successfully.
*
* To see all available options, type "./vendor/bin/lazydoctor --help".
*
* To begin, simply point the "--composer" option at the "composer.json" file
* for your project. Just be aware that your project MUST be based on PSR-4
* autoloading, with ONE OR MORE defined autoload-namespaces in "composer.json".
*
* You can read more about PSR-4 autoloading at the official Composer site:
*
* https://getcomposer.org/doc/04-schema.md#autoload
*
* This tool will ONLY parse classes that properly follow PSR-4 autoloading!
*
* In addition to the composer-file, you'll also have to specify whether you
* want to document virtual "--properties", virtual "--functions", or BOTH.
* Note that we'll ONLY document them when the individual classes support that
* kind of access (when it hasn't disabled its class-option for that feature).
*
* If you don't provide any documentation flags, we'll instead REMOVE all of
* the current class "virtual @property/@method" documentation. That can be
* useful for restoring your files if you don't want to document them anymore.
*
* You should also be aware of "--document-overridden", which will document
* virtual properties or functions even if they're already manually defined
* (overridden) by the class or its parents. That can be useful if you want to
* ensure that you ALWAYS document the virtual properties/functions in all of
* your class files even when it has been overridden somewhere. In that case,
* you should ensure that your overridden properties/functions have the SAME or
* a COMPATIBLE signature & return value, so their auto-docs are still correct.
*
* Tip: You can use the "--validate-only" param to check docs without writing to
* disk. That can be useful when making a non-destructive Git "pre-commit hook"
* to validate your repo and ensure that all files have updated documentation!
*
* This script always uses exit codes to indicate success. A non-zero exit code
* indicates that some files needed new class docs (in "--validate-only" mode),
* or that there was a general problem while processing.
*
* Lastly, you may appreciate the ability to silence all unimportant status
* messages! To do so, simply use ">/dev/null" to send STDOUT to the void...
* That way you'll ONLY see critical status messages during the processing.
*/
set_time_limit(0);
date_default_timezone_set('UTC');
// Verify minimum PHP version.
if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50600) {
fwrite(STDERR, 'LazyDoctor requires PHP 5.6 or higher.'.PHP_EOL);
exit(1);
}
// Register a simple GetOptionKit autoloader. This is fine because
// GetOptionKit has no 3rd party library dependencies.
spl_autoload_register(function ($class) {
// Check if this is a "GetOptionKit" load-request.
static $prefix = 'GetOptionKit\\';
static $len = 13; // strlen($prefix)
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
// Find the "GetOptionKit" source folder.
static $dirs = [
__DIR__.'/../../../corneltek/getoptionkit/src',
__DIR__.'/../vendor/corneltek/getoptionkit/src',
];
$baseDir = null;
foreach ($dirs as $dir) {
if (is_dir($dir) && ($dir = realpath($dir)) !== false) {
$baseDir = $dir;
break;
}
}
if ($baseDir === null) {
return;
}
// Get the relative class name.
$relativeClass = substr($class, $len);
// Generate PSR-4 file path to the class.
$file = sprintf('%s/%s.php', $baseDir, str_replace('\\', '/', $relativeClass));
if (is_file($file)) {
require $file;
}
});
// Parse command line options...
use GetOptionKit\OptionCollection;
use GetOptionKit\OptionParser;
use GetOptionKit\OptionPrinter\ConsoleOptionPrinter;
$specs = new OptionCollection();
$specs->add('c|composer:=file', 'Path to your project\'s composer.json file.');
$specs->add('p|properties?=boolean', 'Document virtual properties (if enabled for the classes).');
$specs->add('f|functions?=boolean', 'Document virtual functions (if enabled for the classes).');
$specs->add('o|document-overridden?=boolean', 'Always document virtual functions/properties even when they have been manually overridden by the class (or its parents).');
$specs->add('w|windows?=boolean', 'Generate Windows-style ("\r\n") documentation line endings instead of the default Unix-style ("\n").');
$specs->add('validate-only?=boolean', 'Validate current docs for all classes but don\'t write anything to disk.');
$specs->add('h|help?=boolean', 'Show all available options.');
try {
$parser = new OptionParser($specs);
$result = $parser->parse($argv);
$options = [
'composer' => isset($result->keys['composer']) ? $result->keys['composer']->value : null,
'properties' => isset($result->keys['properties']) && $result->keys['properties']->value !== false,
'functions' => isset($result->keys['functions']) && $result->keys['functions']->value !== false,
'document-overridden' => isset($result->keys['document-overridden']) && $result->keys['document-overridden']->value !== false,
'windows' => isset($result->keys['windows']) && $result->keys['windows']->value !== false,
'validate-only' => isset($result->keys['validate-only']) && $result->keys['validate-only']->value !== false,
'help' => isset($result->keys['help']) && $result->keys['help']->value !== false,
];
} catch (Exception $e) {
// Warns in case of invalid option values.
fwrite(STDERR, $e->getMessage().PHP_EOL);
exit(1);
}
// Verify options...
echo '[ LazyDoctor ]'.PHP_EOL.PHP_EOL;
if ($options['composer'] === null || $options['help']) {
if ($options['composer'] === null) {
fwrite(STDERR, 'You must provide the --composer option.'.PHP_EOL.PHP_EOL);
}
$printer = new ConsoleOptionPrinter();
echo 'Available options:'.PHP_EOL.PHP_EOL;
echo $printer->render($specs);
exit($options['composer'] === null && !$options['help'] ? 1 : 0);
}
if ($options['composer']->getBasename() !== 'composer.json') {
fwrite(STDERR, 'You must point to your project\'s composer.json file.'.PHP_EOL.'You used: "'.$options['composer']->getRealPath().'".'.PHP_EOL);
exit(1);
}
// Decode the composer.json file...
$json = @json_decode(file_get_contents($options['composer']->getRealPath()), true);
if ($json === null) {
fwrite(STDERR, 'Unable to decode composer.json.'.PHP_EOL);
exit(1);
}
// Determine the project folder's real root path...
$projectRoot = $options['composer']->getPathInfo()->getRealPath();
// Determine their namespace PSR-4 paths via their project's composer.json...
$namespaces = [];
foreach (['autoload', 'autoload-dev'] as $type) {
if (!isset($json[$type]['psr-4']) || !is_array($json[$type]['psr-4'])) {
continue;
}
foreach ($json[$type]['psr-4'] as $namespace => $dir) {
// We don't support composer's empty "fallback" namespaces.
if ($namespace === '') {
fwrite(STDERR, 'Encountered illegal unnamed PSR-4 autoload namespace in composer.json.'.PHP_EOL);
exit(1);
}
// Ensure that the namespace ends in backslash.
if (substr_compare($namespace, '\\', strlen($namespace) - 1, 1) !== 0) {
fwrite(STDERR, 'Encountered illegal namespace "'.$namespace.'" (does not end in backslash) in composer.json.'.PHP_EOL);
exit(1);
}
// Ensure that the value is a string.
// NOTE: We allow empty strings, which corresponds to root folder.
if (!is_string($dir)) {
fwrite(STDERR, 'Encountered illegal non-string value for namespace "'.$namespace.'".'.PHP_EOL);
exit(1);
}
// Now resolve the path name...
$path = sprintf('%s/%s', $projectRoot, $dir);
$realpath = realpath($path);
if ($realpath === false) {
fwrite(STDERR, 'Unable to resolve real path for "'.$path.'".'.PHP_EOL);
exit(1);
}
// We don't allow the same directory to be defined multiple times.
if (isset($namespaces[$realpath])) {
fwrite(STDERR, 'Encountered duplicate namespace directory "'.$realpath.'" in composer.json.'.PHP_EOL);
exit(1);
}
// And we're done! The namespace and its path have been resolved.
$namespaces[$realpath] = $namespace;
}
}
// Verify that we found some namespaces...
if (empty($namespaces)) {
fwrite(STDERR, 'There are no PSR-4 autoload namespaces in your composer.json.'.PHP_EOL);
exit(1);
}
// Now load the project's autoload.php file.
// NOTE: This is necessary so that we can autoload their classes...
$autoload = sprintf('%s/vendor/autoload.php', $projectRoot);
$realautoload = realpath($autoload);
if ($realautoload === false) {
fwrite(STDERR, 'Unable to find the project\'s Composer autoloader ("'.$autoload.'").'.PHP_EOL);
exit(1);
}
require $realautoload;
// Verify that their project's autoloader contains LazyJsonMapper...
if (!class_exists('\LazyJsonMapper\LazyJsonMapper', true)) { // TRUE = Autoload.
fwrite(STDERR, 'Target project doesn\'t contain the LazyJsonMapper library.'.PHP_EOL);
exit(1);
}
// Alright, display the current options...
echo 'Project: "'.$projectRoot.'".'.PHP_EOL
.'- Documentation Line Endings: '.($options['windows'] ? 'Windows ("\r\n")' : 'Unix ("\n")').'.'.PHP_EOL
.'- ['.($options['properties'] ? 'X' : ' ').'] Document Virtual Properties ("@property").'.PHP_EOL
.'- ['.($options['functions'] ? 'X' : ' ').'] Document Virtual Functions ("@method").'.PHP_EOL
.'- ['.($options['document-overridden'] ? 'X' : ' ').'] Document Overridden Properties/Functions.'.PHP_EOL;
if ($options['validate-only']) {
echo '- This is a validation run. Nothing will be written to disk.'.PHP_EOL;
}
// We can now use our custom classes, since the autoloader has been imported...
use LazyJsonMapper\Exception\LazyJsonMapperException;
use LazyJsonMapper\Export\PropertyDescription;
use LazyJsonMapper\Property\PropertyMapCache;
use LazyJsonMapper\Property\PropertyMapCompiler;
use LazyJsonMapper\Utilities;
/**
* Automatic LazyJsonMapper-class documentation generator.
*
* @copyright 2017 The LazyJsonMapper Project
* @license http://www.apache.org/licenses/LICENSE-2.0
* @author SteveJobzniak (https://github.com/SteveJobzniak)
*/
class LazyClassDocumentor
{
/** @var PropertyMapCache */
private static $_propertyMapCache;
/** @var array */
private $_compiledPropertyMapLink;
/** @var ReflectionClass */
private $_reflector;
/** @var array */
private $_options;
/** @var string Newline sequence. */
private $_nl;
/**
* Constructor.
*
* @param string $class
* @param array $options
*
* @throws ReflectionException
*/
public function __construct(
$class,
array $options)
{
if (self::$_propertyMapCache === null) {
self::$_propertyMapCache = new PropertyMapCache();
}
$this->_reflector = new ReflectionClass($class);
$this->_options = $options;
$this->_nl = $options['windows'] ? "\r\n" : "\n";
}
/**
* Process the current class.
*
* @throws ReflectionException
* @throws LazyJsonMapperException
*
* @return bool `TRUE` if on-disk file has correct docs, otherwise `FALSE`.
*/
public function process()
{
// Only process user-defined classes (never any built-in PHP classes).
if (!$this->_reflector->isUserDefined()) {
return true;
}
// There's nothing to do if this isn't a LazyJsonMapper subclass.
// NOTE: This properly skips "\LazyJsonMapper\LazyJsonMapper" itself.
if (!$this->_reflector->isSubclassOf('\LazyJsonMapper\LazyJsonMapper')) {
return true;
}
// Compile this class property map if not yet built and cached.
$thisClassName = $this->_reflector->getName();
if (!isset(self::$_propertyMapCache->classMaps[$thisClassName])) {
try {
PropertyMapCompiler::compileClassPropertyMap( // Throws.
self::$_propertyMapCache,
$thisClassName
);
} catch (Exception $e) {
fwrite(STDERR, '> Unable to compile the class property map for "'.$thisClassName.'". Reason: '.$e->getMessage().PHP_EOL);
return false;
}
}
// Now link to the property map cache for our current class.
$this->_compiledPropertyMapLink = &self::$_propertyMapCache->classMaps[$thisClassName];
// Get the current class comment (string if ok, FALSE if none exists).
$currentDocComment = $this->_reflector->getDocComment();
if (is_string($currentDocComment)) {
$currentDocComment = trim($currentDocComment);
}
// Extract all relevant lines from the current comment.
$finalDocLines = $this->_extractRelevantLines($currentDocComment);
// Generate the automatic summary line (classname followed by period).
$autoSummaryLine = $this->_reflector->getShortName().'.';
// If the 1st line is a classname followed by a period, update the name.
// NOTE: This ensures that we update all outdated auto-added classnames,
// and the risk of false positives is very low since we only document
// `LazyJsonMapper`-based classes with a `OneWord.`-style summary line.
// NOTE: Regex is from http://php.net/manual/en/language.oop5.basic.php,
// and yes we must run it in NON-UNICODE MODE, so that it parses on a
// byte by byte basis exactly like the real PHP classname interpreter.
if (
isset($finalDocLines[0]) // The 1st line MUST exist to proceed.
&& preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\.$/', $finalDocLines[0])
) {
$finalDocLines[0] = $autoSummaryLine;
}
// Generate the magic documentation lines for the current class.
$magicDocLines = $this->_generateMagicDocs();
if (!empty($magicDocLines)) {
// If there are no lines already... add the automatic summary line.
if (empty($finalDocLines)) {
$finalDocLines[] = $autoSummaryLine;
}
// Check the 1st char of the 1st line. If it's an @tag of any kind,
// insert automatic summary line at top and empty line after that.
elseif ($finalDocLines[0][0] === '@') {
array_unshift(
$finalDocLines,
$autoSummaryLine,
''
);
}
$finalDocLines[] = ''; // Add empty line before our magic docs.
$finalDocLines = array_merge($finalDocLines, array_values($magicDocLines));
}
unset($magicDocLines);
// Generate the final doc-comment that this class is supposed to have.
if (!empty($finalDocLines)) {
// This will generate even if the class only contained an existing
// summary/tags and nothing was added by our magic handler.
foreach ($finalDocLines as &$line) {
$line = ($line === '' ? ' *' : " * {$line}");
}
unset($line);
$finalDocComment = sprintf(
'/**%s%s%s */',
$this->_nl,
implode($this->_nl, $finalDocLines),
$this->_nl
);
} else {
// The FALSE signifies that we want no class doc-block at all...
$finalDocComment = false;
}
unset($finalDocLines);
// There's nothing to do if the doc-comment is already correct.
// NOTE: Both values are FALSE if no doc-comment exists and none wanted.
if ($currentDocComment === $finalDocComment) {
return true;
}
// The docs mismatch. If this is a validate-run, just return false now.
if ($this->_options['validate-only']) {
fwrite(STDERR, '> Outdated class docs encountered in "'.$thisClassName.'". Aborting scan...'.PHP_EOL);
return false;
}
// Load the contents of the file...
$classFileName = $this->_reflector->getFileName();
$fileLines = @file($classFileName);
if ($fileLines === false) {
fwrite(STDERR, '> Unable to read class file from disk: "'.$classFileName.'".'.PHP_EOL);
return false;
}
// Split the file into lines BEFORE the class and lines AFTER the class.
$classLine = $this->_reflector->getStartLine();
$startLines = array_slice($fileLines, 0, $classLine - 1);
$endLines = array_slice($fileLines, $classLine - 1);
unset($fileLines);
// Insert the new class documentation using a very careful algorithm.
if ($currentDocComment !== false) {
// Since the class already had PHPdoc, remove it and insert new doc.
// NOTE: A valid PHPdoc (getDocComment()) always starts with
// "/**[whitespace]". If it's just a "/*" or something like
// "/**Foo", then it's not detected by getDocComment(). However, the
// comment may be several lines above the class. So we'll have to do
// an intelligent search to find the old class-comment. As for the
// ending tag "*/", PHP doesn't care about whitespace around that.
// And it also doesn't let the user escape the "*/", which means
// that if we see that sequence we KNOW it's the end of a comment!
// NOTE: We'll search for the latest "/**[whitespace]" block and
// remove all lines from that until its closest "*/".
$deleteFrom = null;
$deleteTo = null;
for ($i = count($startLines) - 1; $i >= 0; --$i) {
if (strpos($startLines[$i], '*/') !== false) {
$deleteTo = $i;
}
if (preg_match('/^\s*\/\*\*\s/u', $startLines[$i])) {
$deleteFrom = $i;
break;
}
}
// Ensure that we have found valid comment-offsets.
if ($deleteFrom === null || $deleteTo === null || $deleteTo < $deleteFrom) {
fwrite(STDERR, '> Unable to parse current class comment on disk: "'.$classFileName.'".'.PHP_EOL);
return false;
}
// Now update the startLines array to replace the doc-comment...
foreach ($startLines as $k => $v) {
if ($k === $deleteFrom && $finalDocComment !== false) {
// We've found the first line of the old comment, and we
// have a new comment. So replace that array entry.
$startLines[$k] = $finalDocComment.$this->_nl;
} elseif ($k >= $deleteFrom && $k <= $deleteTo) {
// Delete all other comment lines, including the first line
// if we had no new doc-comment.
unset($startLines[$k]);
}
// Break if we've reached the final line to delete.
if ($k >= $deleteTo) {
break;
}
}
} elseif ($finalDocComment !== false) {
// There's no existing doc-comment. Just add ours above the class.
// NOTE: This only does something if we had a new comment to insert,
// which we SHOULD have since we came this far in this scenario...
$startLines[] = $finalDocComment.$this->_nl;
}
// Generate the new file contents.
$newFileContent = implode($startLines).implode($endLines);
unset($startLines);
unset($endLines);
// Perform an atomic file-write to disk, which ensures that we will
// never be able to corrupt the class-files on disk via partial writes.
$written = Utilities::atomicWrite($classFileName, $newFileContent);
if ($written !== false) {
echo '> Wrote updated class documentation to disk: "'.$classFileName.'".'.PHP_EOL;
return true;
} else {
fwrite(STDERR, '> Unable to write new class documentation to disk: "'.$classFileName.'".'.PHP_EOL);
return false;
}
}
/**
* Extracts all relevant lines from a doc-comment.
*
* @param string $currentDocComment
*
* @return array
*/
private function _extractRelevantLines(
$currentDocComment)
{
if (!is_string($currentDocComment)) {
return [];
}
// Remove the leading and trailing doc-comment tags (/** and */).
$currentDocComment = preg_replace('/(^\s*\/\*\*\s*|\s*\*\/$)/u', '', $currentDocComment);
// Process all lines. Skip all @method and @property lines.
$relevantLines = [];
$lines = preg_split('/\r?\n|\r/u', $currentDocComment);
foreach ($lines as $line) {
// Remove leading and trailing whitespace, and leading asterisks.
$line = trim(preg_replace('/^\s*\*+/u', '', $line));
// Skip this line if it's a @method or @property line.
// NOTE: Removing them is totally safe, because the LazyJsonMapper
// class has marked all of its magic property/function handlers as
// final, which means that people's subclasses CANNOT override them
// to add their own magic methods/properties. So therefore we KNOW
// that ALL existing @method/@property class doc lines belong to us!
if (preg_match('/^@(?:method|property)/u', $line)) {
continue;
}
$relevantLines[] = $line;
}
// Remove trailing empty lines from the relevant lines.
for ($i = count($relevantLines) - 1; $i >= 0; --$i) {
if ($relevantLines[$i] === '') {
unset($relevantLines[$i]);
} else {
break;
}
}
// Remove leading empty lines from the relevant lines.
foreach ($relevantLines as $k => $v) {
if ($v !== '') {
break;
}
unset($relevantLines[$k]);
}
// Return a re-indexed (properly 0-indexed) array.
return array_values($relevantLines);
}
/**
* Generate PHPdoc lines for all magic properties and functions.
*
* @throws ReflectionException
* @throws LazyJsonMapperException
*
* @return array
*/
private function _generateMagicDocs()
{
// Check whether we should (and can) document properties and functions.
$documentProperties = $this->_options['properties'] && $this->_reflector->getConstant('ALLOW_VIRTUAL_PROPERTIES');
$documentFunctions = $this->_options['functions'] && $this->_reflector->getConstant('ALLOW_VIRTUAL_FUNCTIONS');
if (!$documentProperties && !$documentFunctions) {
return [];
}
// Export all JSON properties, with RELATIVE class-paths when possible.
// NOTE: We will document ALL properties. Even ones inherited from
// parents/imported maps. This ensures that users who are manually
// reading the source code can see EVERYTHING without needing an IDE.
$properties = [];
$ownerClassName = $this->_reflector->getName();
foreach ($this->_compiledPropertyMapLink as $propName => $propDef) {
$properties[$propName] = new PropertyDescription( // Throws.
$ownerClassName,
$propName,
$propDef,
true // Use relative class-paths when possible.
);
}
// Build the magic documentation...
$magicDocLines = [];
foreach (['functions', 'properties'] as $docType) {
if (($docType === 'functions' && !$documentFunctions)
|| ($docType === 'properties' && !$documentProperties)) {
continue;
}
// Generate all lines for the current magic tag type...
$lineStorage = [];
foreach ($properties as $property) {
if ($docType === 'functions') {
// We will only document useful functions (not the "has",
// since those are useless for properties that are fully
// defined in the class map).
foreach (['get', 'set', 'is', 'unset'] as $funcType) {
// Generate the function name, ie "getSomething", and
// skip this function if it's already defined as a REAL
// (overridden) function in this class or its parents.
$functionName = $funcType.$property->func_case;
if (!$this->_options['document-overridden'] && $this->_reflector->hasMethod($functionName)) {
continue;
}
// Alright, the function doesn't exist as a real class
// function, or the user wants to document it anyway...
// Document it via its calculated signature.
// NOTE: Classtypes use paths relative to current class!
$functionSignature = $property->{'function_'.$funcType};
$lineStorage[$functionName] = sprintf('@method %s', $functionSignature);
}
} elseif ($docType === 'properties') {
// Skip this property if it's already defined as a REAL
// (overridden) property in this class or its parents.
if (!$this->_options['document-overridden'] && $this->_reflector->hasProperty($property->name)) {
continue;
}
// Alright, the property doesn't exist as a real class
// property, or the user wants to document it anyway...
// Document it via its calculated signature.
// NOTE: Classtypes use paths relative to current class!
$lineStorage[$property->name] = sprintf(
'@property %s $%s',
$property->type,
$property->name
);
}
}
// Skip this tag type if there was nothing to document...
if (empty($lineStorage)) {
continue;
}
// Insert empty line separators between different magic tag types.
if (!empty($magicDocLines)) {
$magicDocLines[] = '';
}
// Reorder lines by name and add them to the magic doc lines.
// NOTE: We use case sensitivity so that "getComments" and
// "getCommentThreads" etc aren't placed next to each other.
ksort($lineStorage, SORT_NATURAL); // Case-sensitive natural order.
$magicDocLines = array_merge($magicDocLines, array_values($lineStorage));
}
return $magicDocLines;
}
}
// Now process all PHP files under all of the project's namespace folders.
foreach ($namespaces as $realpath => $namespace) {
echo PHP_EOL.'Processing namespace "'.$namespace.'".'.PHP_EOL.'- Path: "'.$realpath.'".'.PHP_EOL;
$realpathlen = strlen($realpath);
$iterator = new RegexIterator(
new RecursiveIteratorIterator(new RecursiveDirectoryIterator($realpath)),
'/\.php$/i', RecursiveRegexIterator::GET_MATCH
);
foreach ($iterator as $file => $ext) {
// Determine the real path to the file (compatible with $realpath).
$realfile = realpath($file);
if ($realfile === false) {
fwrite(STDERR, 'Unable to determine real path to file "'.$file.'".'.PHP_EOL);
exit(1);
}
// Now ensure that the file starts with the expected path...
if (strncmp($realpath, $realfile, $realpathlen) !== 0) {
fwrite(STDERR, 'Unexpected path to file "'.$realfile.'". Does not match project path.'.PHP_EOL);
exit(1);
}
$class = substr($realfile, $realpathlen);
// Remove the leading slash for the folder...
if ($class[0] !== '/' && $class[0] !== '\\') {
fwrite(STDERR, 'Unexpected path to file "'.$realfile.'". Does not match project path.'.PHP_EOL);
exit(1);
}
$class = substr($class, 1);
// And now just generate the final class name...
$class = sprintf(
'%s%s',
$namespace,
str_replace('/', '\\', preg_replace('/\.php$/ui', '', $class))
);
// Some files may not contain classes. For example, some people have
// functions.php files with functions, etc. So before we proceed, just
// ensure that the generated class name actually exists.
// NOTE: class_exists() ignores interfaces. Only finds classes. Good.
if (!class_exists($class, true)) { // TRUE = Autoload.
continue;
}
// Now process the current class.
$documentor = new LazyClassDocumentor($class, $options);
$result = $documentor->process();
if (!$result) {
if ($options['validate-only']) {
fwrite(STDERR, '> One or more files need updated class documentation or contain other errors.'.PHP_EOL);
} else {
fwrite(STDERR, '> Error while processing class "'.$class.'". Aborting...'.PHP_EOL);
}
exit(1);
}
}
}