2018-12-21 11:28:44 +08:00

443 lines
12 KiB
PHP

<?php
/**
* Laravel IDE Helper Generator
*
* @author Barry vd. Heuvel <barryvdh@gmail.com>
* @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl)
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link https://github.com/barryvdh/laravel-ide-helper
*/
namespace Barryvdh\LaravelIdeHelper;
use Barryvdh\Reflection\DocBlock;
use Barryvdh\Reflection\DocBlock\Context;
use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer;
use Barryvdh\Reflection\DocBlock\Tag\MethodTag;
use ReflectionClass;
class Alias
{
protected $alias;
protected $facade;
protected $extends = null;
protected $extendsClass = null;
protected $extendsNamespace = null;
protected $classType = 'class';
protected $short;
protected $namespace = '__root';
protected $root = null;
protected $classes = array();
protected $methods = array();
protected $usedMethods = array();
protected $valid = false;
protected $magicMethods = array();
protected $interfaces = array();
protected $phpdoc = null;
/**
* @param string $alias
* @param string $facade
* @param array $magicMethods
* @param array $interfaces
*/
public function __construct($alias, $facade, $magicMethods = array(), $interfaces = array())
{
$this->alias = $alias;
$this->magicMethods = $magicMethods;
$this->interfaces = $interfaces;
// Make the class absolute
$facade = '\\' . ltrim($facade, '\\');
$this->facade = $facade;
$this->detectRoot();
if ((!$this->isTrait() && $this->root)) {
$this->valid = true;
} else {
return;
}
$this->addClass($this->root);
$this->detectFake();
$this->detectNamespace();
$this->detectClassType();
$this->detectExtendsNamespace();
if (!empty($this->namespace)) {
//Create a DocBlock and serializer instance
$this->phpdoc = new DocBlock(new ReflectionClass($alias), new Context($this->namespace));
}
if ($facade === '\Illuminate\Database\Eloquent\Model') {
$this->usedMethods = array('decrement', 'increment');
}
}
/**
* Add one or more classes to analyze
*
* @param array|string $classes
*/
public function addClass($classes)
{
$classes = (array)$classes;
foreach ($classes as $class) {
if (class_exists($class) || interface_exists($class)) {
$this->classes[] = $class;
} else {
echo "Class not exists: $class\r\n";
}
}
}
/**
* Check if this class is valid to process.
* @return bool
*/
public function isValid()
{
return $this->valid;
}
/**
* Get the classtype, 'interface' or 'class'
*
* @return string
*/
public function getClasstype()
{
return $this->classType;
}
/**
* Get the class which this alias extends
*
* @return null|string
*/
public function getExtends()
{
return $this->extends;
}
/**
* Get the class short name which this alias extends
*
* @return null|string
*/
public function getExtendsClass()
{
return $this->extendsClass;
}
/**
* Get the namespace of the class which this alias extends
*
* @return null|string
*/
public function getExtendsNamespace()
{
return $this->extendsNamespace;
}
/**
* Get the Alias by which this class is called
*
* @return string
*/
public function getAlias()
{
return $this->alias;
}
/**
* Return the short name (without namespace)
*/
public function getShortName()
{
return $this->short;
}
/**
* Get the namespace from the alias
*
* @return string
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* Get the methods found by this Alias
*
* @return array|Method[]
*/
public function getMethods()
{
if (count($this->methods) > 0) {
return $this->methods;
}
$this->addMagicMethods();
$this->detectMethods();
return $this->methods;
}
/**
* Detect class returned by ::fake()
*/
protected function detectFake()
{
$facade = $this->facade;
if (!method_exists($facade, 'fake')) {
return;
}
$real = $facade::getFacadeRoot();
try {
$facade::fake();
$fake = $facade::getFacadeRoot();
if ($fake !== $real) {
$this->addClass(get_class($fake));
}
} finally {
$facade::swap($real);
}
}
/**
* Detect the namespace
*/
protected function detectNamespace()
{
if (strpos($this->alias, '\\')) {
$nsParts = explode('\\', $this->alias);
$this->short = array_pop($nsParts);
$this->namespace = implode('\\', $nsParts);
} else {
$this->short = $this->alias;
}
}
/**
* Detect the extends namespace
*/
protected function detectExtendsNamespace()
{
if (strpos($this->extends, '\\') !== false) {
$nsParts = explode('\\', $this->extends);
$this->extendsClass = array_pop($nsParts);
$this->extendsNamespace = implode('\\', $nsParts);
}
}
/**
* Detect the class type
*/
protected function detectClassType()
{
//Some classes extend the facade
if (interface_exists($this->facade)) {
$this->classType = 'interface';
$this->extends = $this->facade;
} else {
$this->classType = 'class';
if (class_exists($this->facade)) {
$this->extends = $this->facade;
}
}
}
/**
* Get the real root of a facade
*
* @return bool|string
*/
protected function detectRoot()
{
$facade = $this->facade;
try {
//If possible, get the facade root
if (method_exists($facade, 'getFacadeRoot')) {
$root = get_class($facade::getFacadeRoot());
} else {
$root = $facade;
}
//If it doesn't exist, skip it
if (!class_exists($root) && !interface_exists($root)) {
return;
}
$this->root = $root;
//When the database connection is not set, some classes will be skipped
} catch (\PDOException $e) {
$this->error(
"PDOException: " . $e->getMessage() .
"\nPlease configure your database connection correctly, or use the sqlite memory driver (-M)." .
" Skipping $facade."
);
} catch (\Exception $e) {
$this->error("Exception: " . $e->getMessage() . "\nSkipping $facade.");
}
}
/**
* Detect if this class is a trait or not.
*
* @return bool
*/
protected function isTrait()
{
// Check if the facade is not a Trait
if (function_exists('trait_exists') && trait_exists($this->facade)) {
return true;
}
return false;
}
/**
* Add magic methods, as defined in the configuration files
*/
protected function addMagicMethods()
{
foreach ($this->magicMethods as $magic => $real) {
list($className, $name) = explode('::', $real);
if (!class_exists($className) && !interface_exists($className)) {
continue;
}
$method = new \ReflectionMethod($className, $name);
$class = new \ReflectionClass($className);
if (!in_array($magic, $this->usedMethods)) {
if ($class !== $this->root) {
$this->methods[] = new Method($method, $this->alias, $class, $magic, $this->interfaces);
}
$this->usedMethods[] = $magic;
}
}
}
/**
* Get the methods for one or multiple classes.
*
* @return string
*/
protected function detectMethods()
{
foreach ($this->classes as $class) {
$reflection = new \ReflectionClass($class);
$methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
if ($methods) {
foreach ($methods as $method) {
if (!in_array($method->name, $this->usedMethods)) {
// Only add the methods to the output when the root is not the same as the class.
// And don't add the __*() methods
if ($this->extends !== $class && substr($method->name, 0, 2) !== '__') {
$this->methods[] = new Method(
$method,
$this->alias,
$reflection,
$method->name,
$this->interfaces
);
}
$this->usedMethods[] = $method->name;
}
}
}
// Check if the class is macroable
$traits = collect($reflection->getTraitNames());
if ($traits->contains('Illuminate\Support\Traits\Macroable')) {
$properties = $reflection->getStaticProperties();
$macros = isset($properties['macros']) ? $properties['macros'] : [];
foreach ($macros as $macro_name => $macro_func) {
if (!in_array($macro_name, $this->usedMethods)) {
// Add macros
$this->methods[] = new Macro(
$this->getMacroFunction($macro_func),
$this->alias,
$reflection,
$macro_name,
$this->interfaces
);
$this->usedMethods[] = $macro_name;
}
}
}
}
}
/**
* @param $macro_func
*
* @return \ReflectionFunctionAbstract
* @throws \ReflectionException
*/
protected function getMacroFunction($macro_func)
{
if (is_array($macro_func) && is_callable($macro_func)) {
return new \ReflectionMethod($macro_func[0], $macro_func[1]);
}
return new \ReflectionFunction($macro_func);
}
/*
* Get the docblock for this alias
*
* @param string $prefix
* @return mixed
*/
public function getDocComment($prefix = "\t\t")
{
$serializer = new DocBlockSerializer(1, $prefix);
if ($this->phpdoc) {
$this->removeDuplicateMethodsFromPhpDoc();
return $serializer->getDocComment($this->phpdoc);
}
return '';
}
/**
* Removes method tags from the doc comment that already appear as functions inside the class.
* This prevents duplicate function errors in the IDE.
*
* @return void
*/
protected function removeDuplicateMethodsFromPhpDoc()
{
$methodNames = array_map(function (Method $method) {
return $method->getName();
}, $this->getMethods());
foreach ($this->phpdoc->getTags() as $tag) {
if ($tag instanceof MethodTag && in_array($tag->getMethodName(), $methodNames)) {
$this->phpdoc->deleteTag($tag);
}
}
}
/**
* Output an error.
*
* @param string $string
* @return void
*/
protected function error($string)
{
echo $string . "\r\n";
}
}