├── .styleci.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── scripts ├── check-for-duplicate-lines.sh ├── code-style.sh └── mess-detect.sh └── src ├── Console └── Commands │ └── RequireCommand.php ├── Exceptions ├── InvalidPackageNameException.php ├── ServiceProviderAlreadyRegisteredException.php └── ServiceProvidersVariableMissingException.php ├── LaravelRequireServiceProvider.php └── Support ├── ClassInformationParser.php ├── ExternalShellCommand.php ├── FacadeClassLoader.php ├── FilenameFilter.php ├── Packages ├── LaravelPackage.php ├── PackageFileLocator.php ├── PackageFileScanner.php ├── PackageFilesFilterIterator.php ├── PackageItemInstaller.php └── Packages.php ├── ProjectConfiguration.php ├── RegEx.php ├── RegisteredItemInformation.php ├── RequireFile.php └── Rules ├── FacadeRule.php ├── MatchFilenameRuleContract.php ├── MatchSourceCodeRuleContract.php └── ServiceProviderRule.php /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | 3 | finder: 4 | exclude: 5 | - "tests" 6 | - "node_modules" 7 | - "storage" 8 | - "vendor" 9 | - "scripts" 10 | not-name: 11 | - "AcceptanceTester.php" 12 | - "FunctionalTester.php" 13 | - "UnitTester.php" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 - 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## laravel-require 2 | --- 3 | 4 | This Laravel 5 package provides a `require:package` artisan command, which first installs a package via composer, then attempts to automatically register its service provider(s) and facades. 5 | 6 | This makes it even easier to install a Laravel package! 7 | 8 | *Written and tested with Laravel 5.4.* 9 | 10 | --- 11 | #### Installation 12 | 13 | You may install this package using composer: 14 | `composer require patinthehat/laravel-require` 15 | 16 | Once it's installed, you must add the service provider to the 'providers' section in your `config/app.php` file: 17 | 18 | ```php 19 | LaravelRequire\LaravelRequireServiceProvider::class, 20 | ``` 21 | 22 | You will now have a `require:package {package-name}` command available in artisan. It will attempt to automatically register the service provider for a package after installation, and will let you know if it is unable to do so. If this happens, you will have to register the package manually. 23 | 24 | --- 25 | #### Requirements 26 | 27 | In order for `laravel-require` to work properly, you must either have `composer.phar` in your project's base directory or have the `composer` command available in your environment's PATH variable. 28 | 29 | --- 30 | #### Example Usage 31 | 32 | ``` 33 | $ php artisan require:package laracasts/flash 34 | ``` 35 | This installs and registers the `flash` package from laracasts. 36 | 37 | ``` 38 | $ php artisan require:package laracasts/flash --register-only 39 | ``` 40 | This will skip running the `composer require` command entirely, and only attempt to register the package's Service Providers and Facades. 41 | 42 | --- 43 | #### How it works 44 | 45 | `laravel-require` first creates a list of files in the package that might contain a Service Provider or Facade. It first attempts to locate Service Providers/Facades through matching filenames. If this fails, the contents of the files are scanned to locate the Service Providers and Facades. 46 | 47 | --- 48 | #### License 49 | 50 | This package is open-source software, released under the [MIT license](LICENSE). 51 | 52 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "patinthehat/laravel-require", 3 | "description": "Add a package with composer, and automatically registers the service provider.", 4 | "keywords": ["framework", "laravel", "composer", "automation", "laravel-package"], 5 | "license": "MIT", 6 | "homepage": "https://permafrost-dev.com/laravel-require/", 7 | "authors": [ 8 | { 9 | "name": "Patrick Organ", 10 | "email": "patrick@permafrost-software.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.6.4", 15 | "symfony/console": "~3.2", 16 | "symfony/process": "~3.2" 17 | }, 18 | "require-dev": { 19 | "mockery/mockery": "~0.9.4", 20 | "phpunit/phpunit": "~5.7" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "LaravelRequire\\": "src/" 25 | } 26 | }, 27 | "config": { 28 | "sort-packages": true 29 | }, 30 | 31 | 32 | "minimum-stability": "dev", 33 | "prefer-stable": true 34 | } 35 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/Unit 14 | 15 | 16 | 17 | 18 | ./src 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /scripts/check-for-duplicate-lines.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | cd .. 4 | 5 | phpcpd --fuzzy --progress src/ -------------------------------------------------------------------------------- /scripts/code-style.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | cd .. 4 | 5 | phpcs -va .php_cs -------------------------------------------------------------------------------- /scripts/mess-detect.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | cd .. 4 | 5 | phpmd.sh text cleancode -------------------------------------------------------------------------------- /src/Console/Commands/RequireCommand.php: -------------------------------------------------------------------------------- 1 | config = new ProjectConfiguration(); 50 | $this->packages = new LaravelPackage(); 51 | } 52 | 53 | /** 54 | * Execute the console command. 55 | * 56 | * @return mixed 57 | */ 58 | public function handle() 59 | { 60 | $packageVers = null; 61 | $packageName = $this->argument('package'); 62 | $registerOnly = $this->option('register-only'); 63 | $dryRun = $this->option('dry-run'); 64 | 65 | if (strpos($packageName,':')!==false) { 66 | $parts = explode(':', $packageName); 67 | $packageName = $parts[0]; 68 | $packageVers = $parts[1]; 69 | } 70 | try { 71 | Packages::validatePackageName($packageName); 72 | } catch (InvalidPackageNameException $e) { 73 | $this->comment($e->getMessage()); 74 | return; 75 | } 76 | 77 | if ($dryRun) { 78 | $this->info('[dry-run] run composer require ' . $packageName); 79 | } 80 | 81 | if (! $registerOnly && ! $dryRun) { 82 | $composerRequireCommand = $this->findComposerBinary() . " require $packageName"; 83 | if (! is_null($packageVers)) 84 | $composerRequireCommand .= ':'.$packageVers; 85 | 86 | $this->comment("> composer require $packageName..."); 87 | $cmd = new ExternalShellCommand($composerRequireCommand); 88 | $cmd->run(); 89 | } 90 | 91 | $splist = (array) Packages::findPackageFilesToRegister($packageName); 92 | $providers = []; 93 | $facades = []; 94 | $items = []; 95 | 96 | foreach ($splist as $info) { 97 | switch ($info->type) { 98 | case RegisteredItemInformation::SERVICE_PROVIDER_TYPE: 99 | $providers[] = $info; 100 | break; 101 | case RegisteredItemInformation::FACADE_TYPE: 102 | $facades[] = $info; 103 | break; 104 | default: 105 | // nothing to do 106 | } 107 | } 108 | 109 | $installer = new PackageItemInstaller($this); 110 | $items = $installer->install($providers, 'Service Provider', null); 111 | $items2 = $installer->install($facades, 'Facade', null); 112 | $items = array_merge($items, $items2); 113 | 114 | $installCounter = 0; 115 | $failedCounter = 0; 116 | foreach ($items as $item) { 117 | if (! $item || ! $item->filename) 118 | continue; 119 | 120 | try { 121 | $this->info("Registering " . $item->displayName() . ': ' . $item->qualifiedName() . "..."); 122 | 123 | if ($dryRun) { 124 | $this->info('[dry-run] registerPackageItem'); 125 | } 126 | 127 | if (! $dryRun) { 128 | $p = new LaravelPackage; 129 | $parser = new ClassInformationParser(); 130 | $thisBaseNamespace = $parser->getTopLevelNamespace(__NAMESPACE__); 131 | 132 | if ($p->registerPackageItem($item, $thisBaseNamespace)) { 133 | $this->info('...registered successfully.'); 134 | $installCounter++; 135 | } else { 136 | $this->comment('The package and/or service provider did not register or install correctly, or it is not a Laravel package.'); 137 | $failedCounter++; 138 | } 139 | } 140 | } catch (ServiceProvidersVariableMissingException $e) { 141 | $this->comment($e->getMessage()); 142 | } catch (ServiceProviderAlreadyRegisteredException $e) { 143 | $this->comment($e->getMessage()); 144 | } 145 | } // end foreach(providers) 146 | $this->info('Finished. '.$installCounter.' items registered'.($failedCounter > 0 ? ", $failedCounter failed." : '.')); 147 | } 148 | 149 | /** 150 | * Get the composer command for the environment. 151 | * 152 | * @return string 153 | */ 154 | protected function findComposerBinary() 155 | { 156 | if (file_exists(base_path() . '/composer.phar')) { 157 | return '"' . PHP_BINARY . '" composer.phar'; 158 | } 159 | 160 | return 'composer'; 161 | } 162 | 163 | /** 164 | * Get the console command arguments. 165 | * 166 | * @return array 167 | */ 168 | protected function getArguments() 169 | { 170 | return [ 171 | [ 172 | 'package', 173 | InputArgument::REQUIRED, 174 | 'The name the package to install and register.' 175 | ] 176 | ]; 177 | } 178 | 179 | /** 180 | * Get the console command options. 181 | * 182 | * @return array 183 | */ 184 | protected function getOptions() 185 | { 186 | return [ 187 | [ 188 | 'register-only', 189 | 'r', 190 | InputOption::VALUE_NONE, 191 | 'The terminal command that should be assigned.', 192 | 'command:name' 193 | ] 194 | ]; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidPackageNameException.php: -------------------------------------------------------------------------------- 1 | registerCommands(); 25 | } 26 | 27 | /** 28 | * Bootstrap the application events. 29 | * 30 | * @return void 31 | */ 32 | public function boot() 33 | { 34 | // 35 | } 36 | 37 | /** 38 | * Register the artisan commands. 39 | * 40 | * @return void 41 | */ 42 | private function registerCommands() 43 | { 44 | $this->commands([ 45 | \LaravelRequire\Console\Commands\RequireCommand::class 46 | ]); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/Support/ClassInformationParser.php: -------------------------------------------------------------------------------- 1 | getNamespaceSection($namespace, 1); 32 | } 33 | 34 | /** 35 | * Get the actual classname from provided code. 36 | * @param string $code 37 | * @return string|boolean 38 | */ 39 | public function getClassnameFromSource($code) 40 | { 41 | if (preg_match('/\bclass[\s\r\n]*([a-zA-Z0-9_]+)\b/', $code, $m)==1) { 42 | return $m[1]; 43 | } 44 | 45 | return false; 46 | } 47 | 48 | /** 49 | * Get the declared namespace in the provided code 50 | * @param string $code 51 | * @return string|boolean 52 | */ 53 | public function getNamespaceFromSource($code) 54 | { 55 | if (preg_match('/namespace\s*([^;]+);/', $code, $m)==1) { 56 | return trim($m[1]); 57 | } 58 | 59 | return false; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/Support/ExternalShellCommand.php: -------------------------------------------------------------------------------- 1 | command = $command; 13 | } 14 | 15 | public function run() 16 | { 17 | $process = new Process($this->command, base_path(), null, null, null); 18 | 19 | if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { 20 | $process->setTty(true); 21 | } 22 | 23 | $process->run(function ($type, $line) { 24 | $this->line($line); 25 | }); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/Support/FacadeClassLoader.php: -------------------------------------------------------------------------------- 1 | getLoaderClassname(); 39 | $loaderfn = $this->getLoaderFilename($loaderClassname); 40 | $contents = $this->processFacadeFileContents($loaderClassname, $contents); 41 | 42 | $this->writeDataToTempFile($loaderfn, $contents); 43 | 44 | try { 45 | include_once($loaderfn); 46 | $facadeName = $loaderClassname::getFacadeName(); 47 | } catch(\Exception $e) { 48 | return false; 49 | } finally { 50 | $this->cleanup(); 51 | } 52 | 53 | return ucfirst($facadeName); 54 | } 55 | 56 | protected function writeDataToTempFile($filename, $data) 57 | { 58 | if (!in_array($filename, $this->tempFiles)) 59 | $this->tempFiles[] = $filename; 60 | file_put_contents($filename, $data); 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * Process the contents of the original Facade class, removing namespace & 67 | * uses. Renames the existing classname with a dynamic 68 | * one. Makes all methods public, rename getFacadeAccessor() 69 | * to getFacadeName(). 70 | * @param string $loaderClassname 71 | * @param string $contents 72 | * @return string 73 | */ 74 | protected function processFacadeFileContents($loaderClassname, $contents) 75 | { 76 | $temp = $contents; 77 | $temp = str_replace('namespace ', '//namespace ', $temp); 78 | $temp = preg_replace('/use /', '//use ', $temp); 79 | $temp = preg_replace('/class\s+([a-zA-Z0-9_]+)\s+extends\s+[\\\A-Za-z0-9_]*Facade/', "class $loaderClassname ", $temp); 80 | $temp = preg_replace('/(protected|private)/', 'public', $temp); 81 | $temp = preg_replace('/getFacadeAccessor/', 'getFacadeName', $temp); 82 | //$temp = preg_replace('/protected\s+static\s+function\s+getFacadeAccessor\b/', 'public static function getFacadeName', $temp); 83 | 84 | return $temp; 85 | } 86 | 87 | /** 88 | * Stores the dynamically generated temp classname for use with loading 89 | * the Facade code. 90 | * @param string $refresh 91 | * @return string 92 | */ 93 | protected function getLoaderClassname($refresh = false) 94 | { 95 | static $classname = ''; 96 | if ($classname == '' || $refresh === true) 97 | $classname = (new \ReflectionClass($this))->getShortName().sha1(mt_rand(1, 999999999)); 98 | 99 | return $classname; 100 | } 101 | 102 | /** 103 | * Return the dynamically generated filename for loading the Facade code. 104 | * @param string $classname 105 | * @return string 106 | */ 107 | protected function getLoaderFilename($classname) 108 | { 109 | return "${classname}.laravel-require.facade-loader.php"; 110 | } 111 | 112 | /** 113 | * Remove the temp file that was used to load the facade code. 114 | * @return \LaravelRequire\Support\FacadeClassLoader 115 | */ 116 | protected function cleanup() 117 | { 118 | foreach($this->tempFiles as $file) { 119 | if (file_exists($file)) 120 | unlink($file); 121 | } 122 | $this->tempFiles = []; 123 | return $this; 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/Support/FilenameFilter.php: -------------------------------------------------------------------------------- 1 | generateRegistrationLine($item); 39 | $config = ($projectConfig)->readConfigurationFile(); 40 | $parser = new ClassInformationParser(); 41 | 42 | if (strpos($config, $regline) !== false) { 43 | throw new ServiceProviderAlreadyRegisteredException($item->displayName() . ' ' . $item->qualifiedName() . " is already registered."); 44 | } 45 | 46 | if ($item->type == RegisteredItemInformation::SERVICE_PROVIDER_TYPE) { 47 | // search for our package's registration (laravel-require), so we 48 | // know where to insert the new package registration 49 | $thisBaseNamespace = $parser->getTopLevelNamespace($thisBaseNamespace); 50 | 51 | $thisServiceProviderLine = "$thisBaseNamespace\\${thisBaseNamespace}ServiceProvider::class,"; 52 | $searchLine = $thisServiceProviderLine; 53 | 54 | if (strpos($config, $thisServiceProviderLine) === false) { 55 | throw new ServiceProvidersVariableMissingException("Could not find registration for the $thisBaseNamespace package in config/app.php. " . "Please add it to the end of the service providers array to use this command."); 56 | } 57 | } 58 | 59 | if ($item->type == RegisteredItemInformation::FACADE_TYPE) { 60 | $searchLine = "'aliases' => ["; 61 | // some Facades provided by packages are named 'Facade.php', so we 62 | // will try 63 | // to guess the correct Facade name based on its namespace; 64 | if (strtolower($item->classname) == 'facade') { 65 | $item->name = $parser->getNamespaceSection($item->namespace, 2); 66 | // regenerate the registration line with the updated name 67 | $regline = $this->generateRegistrationLine($item); 68 | } 69 | } 70 | 71 | $count = 0; 72 | $config = str_replace($searchLine, $searchLine . PHP_EOL . " $regline" . PHP_EOL, $config, $count); 73 | 74 | if ($count > 0) { 75 | $projectConfig->writeConfigurationFile($config); 76 | return true; 77 | } 78 | 79 | return false; 80 | } 81 | 82 | /** 83 | * Generate the code needed to register the service provider. 84 | * 85 | * @param RegisteredItemInformation $item 86 | * @return string 87 | */ 88 | protected function generateRegistrationLine(RegisteredItemInformation $item) 89 | { 90 | switch ($item->type) { 91 | case RegisteredItemInformation::SERVICE_PROVIDER_TYPE: 92 | return $item->namespace . "\\" . $item->classname . "::class,"; 93 | 94 | case RegisteredItemInformation::FACADE_TYPE: 95 | return "'" . (strlen($item->name) > 0 ? $item->name : $item->classname) . "' => " . $item->namespace . '\\' . $item->classname . "::class,"; 96 | 97 | default: 98 | return ''; 99 | } 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /src/Support/Packages/PackageFileLocator.php: -------------------------------------------------------------------------------- 1 | packageName = $packageName; 20 | } 21 | 22 | /** 23 | * Recursively scan the package's directory for all files, using a filter 24 | * to strip out files we know won't have a service provider or Facade. 25 | * @return \RecursiveIteratorIterator[] 26 | */ 27 | public function locatePackageFiles() 28 | { 29 | $fileiterator = new \RecursiveDirectoryIterator( 30 | $this->getPackagePath($this->packageName), 31 | \FilesystemIterator::KEY_AS_PATHNAME | 32 | \FilesystemIterator::CURRENT_AS_FILEINFO | 33 | \FilesystemIterator::SKIP_DOTS | 34 | \FilesystemIterator::FOLLOW_SYMLINKS 35 | ); 36 | 37 | //loop through the file list, and apply a filter, removing files that we know 38 | //won't contain a Service Provider or Facade. 39 | $iterator = new \RecursiveIteratorIterator( 40 | new PackageFilesFilterIterator($fileiterator), 41 | \RecursiveIteratorIterator::SELF_FIRST 42 | ); 43 | $result = []; 44 | 45 | //only allow php files 46 | //TODO Implement FilenameFilter class here 47 | foreach ($iterator as $file) { 48 | if ($file->getExtension() == 'php') 49 | $result[] = $file; 50 | } 51 | 52 | return $result; 53 | } 54 | 55 | /** 56 | * Determine the package's installation directory 57 | * @return string 58 | */ 59 | public function getPackagePath() 60 | { 61 | return base_path() . "/vendor/".strtolower($this->packageName); 62 | } 63 | 64 | /** 65 | * Check to see if the package's path exists. 66 | * @return boolean 67 | */ 68 | public function packagePathExists() 69 | { 70 | return is_dir($this->getPackagePath()); 71 | } 72 | 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/Support/Packages/PackageFileScanner.php: -------------------------------------------------------------------------------- 1 | filenameMatch($filename); 32 | if ($filenameMatch !== false) { 33 | $info = new RegisteredItemInformation(); 34 | 35 | $classname = $parser->getClassnameFromSource($contents); 36 | $namespace = $parser->getNamespaceFromSource($contents); 37 | $info->filename($filename) 38 | ->classname($classname) 39 | ->namespace($namespace) 40 | ->name($classname); 41 | 42 | return $info; 43 | } 44 | 45 | $codeMatch = $rule->sourceCodeMatch($contents); 46 | if ($codeMatch !== false && !$info) { 47 | $codeClassname = $codeMatch['class']; 48 | $codeExtends = $codeMatch['extends']; 49 | $codeName = $codeMatch['name']; 50 | $codeType = $codeMatch['type']; 51 | 52 | $info = new RegisteredItemInformation(); 53 | $info->classname($codeClassname) 54 | ->filename($filename) 55 | ->extends($codeExtends) 56 | ->name($codeName); 57 | 58 | $namespace = $parser->getNamespaceFromSource($contents); 59 | $info->namespace($namespace); 60 | return $info; 61 | } 62 | } 63 | return $info; 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/Support/Packages/PackageFilesFilterIterator.php: -------------------------------------------------------------------------------- 1 | regex = new RegEx(); 39 | // compile all of the filters into one regular expression 40 | self::$compiledRejectFilter = $this->regex->compileFiltersToRegEx(self::$rejectFilters); 41 | } 42 | 43 | protected function matches($filename, $filter) 44 | { 45 | if ($this->regex->isRegularExpression($filter)) 46 | return (preg_match($filter, $filename) == 1); // filter is a regular 47 | // expression 48 | 49 | return ($filename == $filter); // filter requires an exact match 50 | } 51 | 52 | /** 53 | * Accept or reject the current file based on the regular expression 54 | * generated 55 | * upon class creation. 56 | * 57 | * @return boolean 58 | */ 59 | public function accept() 60 | { 61 | $filename = $this->current()->getFilename(); 62 | 63 | if ($this->matches($filename, self::$compiledRejectFilter)) 64 | return false; 65 | 66 | return true; 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /src/Support/Packages/PackageItemInstaller.php: -------------------------------------------------------------------------------- 1 | command = $command; 24 | } 25 | 26 | /** 27 | * Returns an array of fully-qualified classnames to register. 28 | * @param string $data 29 | * @param string $type 30 | * @param string $emptyMessage 31 | * @return array 32 | */ 33 | public function install($data, $type, $emptyMessage = null) 34 | { 35 | $result = []; 36 | if (count($data) == 0) { 37 | $this->command->comment(is_null($emptyMessage) ? "No ${type}s found." : null); 38 | return $result; 39 | } 40 | 41 | foreach ($data as $item) { 42 | if ($this->command->confirm("Install $type '$item'?")) 43 | $result[] = $item; 44 | } 45 | 46 | return $result; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/Support/Packages/Packages.php: -------------------------------------------------------------------------------- 1 | locatePackageFiles(); 27 | 28 | foreach ($files as $file) { 29 | $spInfo = $scanner->scanFile($file->getPathname(), new ServiceProviderRule()); 30 | $fInfo = $scanner->scanFile($file->getPathname(), new FacadeRule(new FacadeClassLoader())); 31 | if ($fInfo !== false) 32 | $result[] = $fInfo->type(RegisteredItemInformation::FACADE_TYPE); 33 | if ($spInfo !== false) 34 | $result[] = $spInfo->type(RegisteredItemInformation::SERVICE_PROVIDER_TYPE); 35 | } 36 | 37 | return $result; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/Support/ProjectConfiguration.php: -------------------------------------------------------------------------------- 1 | isBasicCapture($filter) && strpos($filter, '|') !== false); 30 | } 31 | 32 | /** 33 | * Strip off the leading and trailing characters from $filter. 34 | * TODO this is too similar to getFilterExpressionOnly, consider merging into one method 35 | * @param string $filter 36 | * @return string 37 | */ 38 | protected function removeBasicCaptureChars($filter) 39 | { 40 | return substr($filter, 1, strlen($filter) - 2); 41 | } 42 | 43 | /** 44 | * Compiles an array of regular expressions into one expression, 45 | * to avoid multiple calls trying to match each individual 46 | * expression. 47 | * 48 | * @param array $filters 49 | * @return string 50 | */ 51 | public function compileFiltersToRegEx(array $filters) 52 | { 53 | $parts = []; 54 | 55 | foreach ($filters as $filter) { 56 | $expression = $this->getFilterExpressionOnly($filter); 57 | 58 | if ($this->isBasicCaptureWithPipes($expression)) 59 | $expression = $this->removeBasicCaptureChars($expression); 60 | $parts[] = $expression; 61 | } 62 | $result = '/(' . implode('|', $parts) . ')/'; 63 | 64 | return $result; 65 | } 66 | 67 | /** 68 | * Extract the regular expression, removing the leading and trailing '/' chars. 69 | * If $filter isn't a regex, just return the value of $filter unmodified. 70 | * @param string $filter 71 | * @return string 72 | */ 73 | protected function getFilterExpressionOnly($filter) 74 | { 75 | if (!$this->isRegularExpression($filter)) 76 | return $filter; 77 | 78 | return substr($filter, 1, strlen($filter) - 2); 79 | } 80 | 81 | /** 82 | * Checks to see if $filter is a regular expression filter, 83 | * or just a simple text match filter. 84 | * @param string $filter 85 | * @return boolean 86 | */ 87 | public function isRegularExpression($filter) 88 | { 89 | if (strlen($filter) <= 2) 90 | return false; 91 | 92 | $firstchar = $filter[0]; 93 | $lastchar = $filter[strlen($filter) - 1]; 94 | 95 | return (($firstchar == '/') && ($firstchar == $lastchar)); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/Support/RegisteredItemInformation.php: -------------------------------------------------------------------------------- 1 | type = $type; 31 | } 32 | 33 | public function displayName() 34 | { 35 | if ($this->type == self::SERVICE_PROVIDER_TYPE) 36 | return "Service Provider"; 37 | if ($this->type == self::FACADE_TYPE) 38 | return "Facade"; 39 | 40 | return "Unknown"; 41 | } 42 | 43 | /** 44 | * String representation is the fully-qualified classname. 45 | * 46 | * @return string 47 | */ 48 | public function __toString() 49 | { 50 | return $this->qualifiedName(); 51 | } 52 | 53 | /** 54 | * Get the fully-qualified classname 55 | * 56 | * @return string 57 | */ 58 | public function qualifiedName() 59 | { 60 | return $this->namespace . '\\' . $this->classname; 61 | } 62 | 63 | public function package($value) 64 | { 65 | $this->package = $value; 66 | return $this; 67 | } 68 | 69 | public function classname($value) 70 | { 71 | $this->classname = $value; 72 | return $this; 73 | } 74 | 75 | public function extends($value) 76 | { 77 | $this->extends = $value; 78 | return $this; 79 | } 80 | 81 | public function namespace($value) 82 | { 83 | $this->namespace = $value; 84 | return $this; 85 | } 86 | 87 | public function filename($value) 88 | { 89 | $this->filename = $value; 90 | return $this; 91 | } 92 | 93 | public function type($value) 94 | { 95 | $this->type = $value; 96 | return $this; 97 | } 98 | 99 | public function name($value) 100 | { 101 | $this->name = $value; 102 | return $this; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/Support/RequireFile.php: -------------------------------------------------------------------------------- 1 | filename = $filename; 15 | } 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Support/Rules/FacadeRule.php: -------------------------------------------------------------------------------- 1 | facadeLoader = $facadeLoader; 17 | } 18 | 19 | /** 20 | * Test a filename to see if it's a Facade. 21 | * @param string $filename 22 | * @return boolean 23 | */ 24 | public function filenameMatch($filename) 25 | { 26 | return (preg_match('/^[a-zA-Z0-9_]*Facade\.php$/', basename($filename))==1); 27 | } 28 | 29 | /** 30 | * Test the contents of a file to see if the class within it is a Facade class. If 31 | * a match is found, use the smart facade class loader to reliably determine the 32 | * proper name for the Facade. This is done because Facade filenames can be named 33 | * just about anything, including 'Facade.php'. The name is important as it's used 34 | * during Facade registration. 35 | * Returns false if no matches are found, and an array of information if a match is 36 | * found. 37 | * 38 | * @param string $contents 39 | * @return array|boolean 40 | */ 41 | public function sourceCodeMatch($contents) 42 | { 43 | if (preg_match('/\b([a-zA-Z0-9_]+)\s+extends\s+([a-zA-Z0-9_\\\]*Facade)\b/', $contents, $m)==1) { 44 | $facadeName = $this->facadeLoader->load($contents, true); 45 | if ($facadeName !== false) 46 | return ['class'=>$m[1], 'extends'=>$m[2], 'name'=>$facadeName, 'type'=>'facade']; 47 | 48 | return ['class'=>$m[1], 'extends'=>$m[2], 'name'=>$m[1], 'type'=>'facade']; 49 | } 50 | 51 | return false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Support/Rules/MatchFilenameRuleContract.php: -------------------------------------------------------------------------------- 1 | $m[1], 'extends'=>$m[2], 'name'=>$m[1], 'type'=>'serviceprovider']; 38 | } 39 | 40 | return false; 41 | } 42 | } 43 | --------------------------------------------------------------------------------