├── .styleci.yml ├── LICENSE ├── README.md ├── composer.json └── src ├── Console └── Commands │ ├── RequirePackageCommand.php │ └── UnregisterPackageCommand.php ├── Exceptions ├── ConfigurationFileNotFoundException.php ├── InvalidExternalCommandException.php ├── InvalidPackageNameException.php └── PackageDirectoryNotFoundException.php ├── LaravelPackageManagerServiceProvider.php ├── Packages ├── Files │ ├── PackageFileClassifier.php │ ├── PackageFileFilterIterator.php │ ├── PackageFileList.php │ ├── PackageFileLocator.php │ └── Scanner │ │ └── Rules │ │ ├── FacadeScannerRule.php │ │ ├── PackageFileScannerRuleContract.php │ │ └── ServiceProviderScannerRule.php ├── Package.php ├── PackageInstaller.php ├── PackageRegistration.php └── PackageRequirer.php └── Support ├── CommandOptions.php ├── ConfigurationFile.php ├── FacadeClassLoader.php ├── ItemInformation.php ├── Output.php ├── RegEx.php ├── RunExternalCommand.php └── UserPrompt.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 Package Manager ## 2 | --- 3 | 4 | The `Laravel Package Manager` provides fast, yet simple management of packages for your Laravel project. 5 | It allows you to quickly install a package via composer, if necessary, and then automatically register any or all Service Providers and Facades provided by the package. 6 | 7 | --- 8 | ### Installation 9 | --- 10 | 11 | To install, first install with composer: 12 | 13 | composer require patinthehat/laravel-package-manager 14 | 15 | Then, register the service provider by editing `config/app.php` and adding: 16 | 17 | LaravelPackageManager\LaravelPackageManagerServiceProvider::class, 18 | 19 | to the `$providers` array. 20 | 21 | That's it! You now have access to the package manager commands through `artisan`. 22 | 23 | --- 24 | ### Usage 25 | --- 26 | 27 | To install (via composer) a package and register any service providers or Facades it provides, use the `package:require` command: 28 | 29 | package:require [-r|--register-only] [-d|--dev] 30 | 31 | The `--register-only` option skips the composer installation step. 32 | The `--dev` option allows you to install the package in your development dependencies. 33 | 34 | To unregister service providers and facades associated with a package, use the `package:unregister` command: 35 | 36 | package:unregister 37 | 38 | You will be prompted for each Service Provider and Facade, and asked if you would like to unregister it. This does not remove the package from your `vendor/` directory. 39 | 40 | --- 41 | ### License 42 | --- 43 | The `Laravel Package Manager` is open source software, available under the [MIT License](LICENSE). -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "patinthehat/laravel-package-manager", 3 | "description": "Package manager for Laravel 5+.", 4 | "keywords": ["framework", "laravel", "composer", "automation", "laravel-package"], 5 | "license": "MIT", 6 | "homepage": "https://permafrost-dev.com/laravel-package-manager/", 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 | "LaravelPackageManager\\": "src/" 25 | } 26 | }, 27 | "config": { 28 | "sort-packages": true 29 | }, 30 | 31 | 32 | "minimum-stability": "dev", 33 | "prefer-stable": true 34 | } 35 | -------------------------------------------------------------------------------- /src/Console/Commands/RequirePackageCommand.php: -------------------------------------------------------------------------------- 1 | requirer = new PackageRequirer($this); 43 | } 44 | 45 | /** 46 | * Execute the console command. 47 | * 48 | * @return mixed 49 | */ 50 | public function handle() 51 | { 52 | $options = [ 53 | 'register-only' => $this->hasOption('register-only') ? $this->option('register-only') : null, 54 | 'dev' => $this->hasOption('dev') ? $this->option('dev') : null, 55 | ]; 56 | 57 | $this->requirer->require($this->argument('package'), $options); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Console/Commands/UnregisterPackageCommand.php: -------------------------------------------------------------------------------- 1 | output = new Output($this); 53 | $this->userPrompt = new UserPrompt($this->output); 54 | } 55 | 56 | /** 57 | * Execute the console command. 58 | * 59 | * @return mixed 60 | */ 61 | public function handle() 62 | { 63 | $packageName = $this->argument('package'); 64 | 65 | $package = new Package($packageName); 66 | 67 | $locator = new PackageFileLocator($package); 68 | $locator->locateFiles(); 69 | 70 | $classifier = new PackageFileClassifier(); 71 | $classifier->classifyFiles($locator->getFiles()); 72 | 73 | $registrar = new PackageRegistration($this, $this->userPrompt); 74 | $registrar->unregisterAll($package, $classifier->getFiles()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Exceptions/ConfigurationFileNotFoundException.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 24 | $this->registerCommands(); 25 | } 26 | } 27 | 28 | /** 29 | * Bootstrap the application events. 30 | * 31 | * @return void 32 | */ 33 | public function boot() 34 | { 35 | // 36 | } 37 | 38 | /** 39 | * Register the artisan commands. 40 | * 41 | * @return void 42 | */ 43 | private function registerCommands() 44 | { 45 | $this->commands([ 46 | \LaravelPackageManager\Console\Commands\RequirePackageCommand::class, 47 | \LaravelPackageManager\Console\Commands\UnregisterPackageCommand::class, 48 | ]); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Packages/Files/PackageFileClassifier.php: -------------------------------------------------------------------------------- 1 | matchByFilename($filename) || $rule->matchBySourcecode($data)); 25 | if ($result) { 26 | return $rule->matchType(); 27 | } 28 | 29 | return ItemInformation::UNKNOWN; 30 | } 31 | 32 | /** 33 | * Classifies multiple files. 34 | * @param PackageFileList $list 35 | * @return \LaravelPackageManager\Packages\Files\PackageFileClassifier 36 | */ 37 | public function classifyFiles(PackageFileList $list) 38 | { 39 | foreach ($list as $file) { 40 | $ii = new ItemInformation; 41 | $ii->filename = $file->getPathname(); 42 | $rule = new FacadeScannerRule; 43 | $type = $this->classifyFile($ii->filename, $rule); 44 | 45 | if ($type == ItemInformation::UNKNOWN) { 46 | $rule = new ServiceProviderScannerRule; 47 | $type = $this->classifyFile($ii->filename, $rule); 48 | 49 | if ($type != ItemInformation::UNKNOWN) { 50 | $ii = $rule->getInformationFromSourcecode(file_get_contents($file->getPathname())); 51 | $ii->filename = $file->getPathname(); 52 | $ii->type = $type; 53 | } 54 | } 55 | 56 | if ($type != ItemInformation::UNKNOWN) { 57 | $ii = $rule->getInformationFromSourcecode(file_get_contents($file->getPathname())); 58 | $ii->filename = $file->getPathname(); 59 | $ii->type = $rule->matchType(); 60 | } 61 | 62 | if ($ii->type != ItemInformation::UNKNOWN) { 63 | $this->files[$file->getPathname()] = $ii; 64 | } 65 | } 66 | 67 | return $this; 68 | } 69 | 70 | /** 71 | * Returns the files property. 72 | * @return array 73 | */ 74 | public function getFiles() 75 | { 76 | return $this->files; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Packages/Files/PackageFileFilterIterator.php: -------------------------------------------------------------------------------- 1 | regex = new RegEx(); 48 | // compile all of the filters into one regular expression 49 | if (self::$compiledRejectFilter == '') { 50 | self::$compiledRejectFilter = $this->regex->compileFiltersToRegEx(self::$rejectFilters); 51 | } 52 | 53 | if (self::$compiledAcceptFilter == '') { 54 | self::$compiledAcceptFilter = $this->regex->compileFiltersToRegEx(self::$acceptFilters); 55 | } 56 | } 57 | 58 | protected function matches($filename, $filter) 59 | { 60 | if ($this->regex->isRegularExpression($filter)) { 61 | return preg_match($filter, $filename) == 1; 62 | } // filter is a regular 63 | // expression 64 | return $filename == $filter; // filter requires an exact match 65 | } 66 | 67 | /** 68 | * Accept or reject the current file based on the regular expression 69 | * generated 70 | * upon class creation. 71 | * 72 | * @return bool 73 | */ 74 | public function accept() 75 | { 76 | $filename = $this->current()->getFilename(); 77 | 78 | return ! $this->matches($filename, self::$compiledRejectFilter); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Packages/Files/PackageFileList.php: -------------------------------------------------------------------------------- 1 | container; 18 | } 19 | 20 | public function setFiles(array $files) 21 | { 22 | $this->container = $files; 23 | } 24 | 25 | public function add($file) 26 | { 27 | $this->container[] = $file; 28 | } 29 | 30 | public function offsetSet($offset, $value) 31 | { 32 | if (is_null($offset)) { 33 | $this->container[] = $value; 34 | 35 | return; 36 | } 37 | $this->container[$offset] = $value; 38 | } 39 | 40 | public function offsetExists($offset) 41 | { 42 | return isset($this->container[$offset]); 43 | } 44 | 45 | public function offsetUnset($offset) 46 | { 47 | unset($this->container[$offset]); 48 | } 49 | 50 | public function offsetGet($offset) 51 | { 52 | return isset($this->container[$offset]) ? $this->container[$offset] : null; 53 | } 54 | 55 | public function rewind() 56 | { 57 | $this->position = 0; 58 | } 59 | 60 | public function current() 61 | { 62 | return $this->container[$this->position]; 63 | } 64 | 65 | public function key() 66 | { 67 | return $this->position; 68 | } 69 | 70 | public function next() 71 | { 72 | ++$this->position; 73 | } 74 | 75 | public function valid() 76 | { 77 | return isset($this->container[$this->position]); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Packages/Files/PackageFileLocator.php: -------------------------------------------------------------------------------- 1 | package = $package; 23 | $this->files = new PackageFileList; 24 | } 25 | 26 | /** 27 | * Recursively scan the package's directory for all files, using a filter 28 | * to strip out files we know won't have a service provider or Facade. 29 | * @param bool $prioritization - prioritizes files that are most likely 30 | * to contain SPs or Facades to the top of the resulting array. 31 | * @return \LaravelPackageManager\Packages\Files\PackageFileList 32 | */ 33 | public function locateFiles($prioritization = true) 34 | { 35 | $fileiterator = new \RecursiveDirectoryIterator( 36 | $this->package->getPath(), 37 | \FilesystemIterator::KEY_AS_PATHNAME | 38 | \FilesystemIterator::CURRENT_AS_FILEINFO | 39 | \FilesystemIterator::SKIP_DOTS | 40 | \FilesystemIterator::FOLLOW_SYMLINKS 41 | ); 42 | 43 | //loop through the file list, and apply a filter, removing files that we know 44 | //won't contain a Service Provider or Facade. 45 | $iterator = new \RecursiveIteratorIterator( 46 | new PackageFileFilterIterator($fileiterator), 47 | \RecursiveIteratorIterator::SELF_FIRST 48 | ); 49 | 50 | $result = []; 51 | 52 | //only allow php files with a filesize > 0 53 | //TODO Implement FilenameFilter class here 54 | foreach ($iterator as $file) { 55 | if (! $file->isDir() && $file->getExtension() == 'php' && $file->getSize() > 0) { 56 | $result[] = $file; 57 | } 58 | } 59 | 60 | if ($prioritization) { 61 | //sort the files, with files ending with "ServiceProvider" or "Facade" at the top, 62 | //to increase the speed at which we find those classes. 63 | usort($result, function ($a, $b) { 64 | if (ends_with($a, 'ServiceProvider.php') || ends_with($a, 'Facade.php')) { 65 | return -1; 66 | } 67 | if (ends_with($b, 'ServiceProvider.php') || ends_with($b, 'Facade.php')) { 68 | return 1; 69 | } 70 | 71 | return strcmp($a, $b); 72 | }); 73 | } 74 | 75 | $this->files->setFiles($result); 76 | 77 | return $this->getFiles(); 78 | } 79 | 80 | public function getFiles() 81 | { 82 | return $this->files; 83 | } 84 | 85 | public function clearFiles() 86 | { 87 | $this->files = new PackageFileList; 88 | 89 | return $this; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Packages/Files/Scanner/Rules/FacadeScannerRule.php: -------------------------------------------------------------------------------- 1 | initFacadeLoader(); 15 | } 16 | 17 | protected function initFacadeLoader() 18 | { 19 | $this->facadeLoader = new FacadeClassLoader; 20 | $this->facadeLoader->refresh(); 21 | } 22 | 23 | /** 24 | * Match the classtype by filename. 25 | * @param string $filename 26 | * @return bool 27 | */ 28 | public function matchByFilename($filename) 29 | { 30 | return preg_match('/[a-zA-Z0-9_]*Facade\.php$/', basename($filename)) == 1; 31 | } 32 | 33 | /** 34 | * Match the classtype by scanning the source code. 35 | * @param string $code 36 | * @return bool 37 | */ 38 | public function matchBySourcecode($code) 39 | { 40 | return preg_match('/\b([a-zA-Z0-9_]+)\s+extends\s+([a-zA-Z0-9_\\\]*Facade)\b/', $code) == 1; 41 | } 42 | 43 | /** 44 | * Extract information about this facade from its source code. 45 | * @param string $code 46 | * @return \LaravelPackageManager\Support\ItemInformation 47 | */ 48 | public function getInformationFromSourcecode($code) 49 | { 50 | $ii = new ItemInformation; 51 | $ii->type(false); 52 | 53 | if (preg_match('/\b([a-zA-Z0-9_]+)\s+extends\s+([a-zA-Z0-9_\\\]*Facade)\b/', $code, $m) == 1) { 54 | $this->initFacadeLoader(); 55 | $facadeName = $this->facadeLoader->load($ii->filename, $code); //($code, true); 56 | 57 | $ii->classname($m[1])->extends($m[2])->type($this->matchType()); 58 | $ii->name($facadeName !== false ? $facadeName : $m[1]); 59 | } 60 | 61 | $m = []; 62 | if (preg_match('/namespace ([^;]+)/', $code, $m) == 1) { 63 | $ii->namespace($m[1]); 64 | } 65 | 66 | return $ii; 67 | } 68 | 69 | /** 70 | * The class type matched by this rule. 71 | * @return int 72 | */ 73 | public function matchType() 74 | { 75 | return ItemInformation::FACADE; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Packages/Files/Scanner/Rules/PackageFileScannerRuleContract.php: -------------------------------------------------------------------------------- 1 | type(false); 38 | 39 | if (preg_match('/([a-zA-Z0-9_]+)\s+extends\s+([a-zA-Z0-9_\\\]*ServiceProvider)/', $code, $m) == 1) { 40 | $ii->classname($m[1])->extends($m[2])->name($m[1])->type($this->matchType()); 41 | } 42 | 43 | $m = []; 44 | if (preg_match('/namespace ([^;]+)/', $code, $m) == 1) { 45 | $ii->namespace($m[1]); 46 | } 47 | 48 | return $ii; 49 | } 50 | 51 | /** 52 | * The class type matched by this rule. 53 | * @return int 54 | */ 55 | public function matchType() 56 | { 57 | return ItemInformation::SERVICE_PROVIDER; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Packages/Package.php: -------------------------------------------------------------------------------- 1 | name = strtolower(trim($name)); 39 | if (strpos($this->name, ':') !== false) { 40 | $parts = explode(':', $this->name); 41 | $this->name = $parts[0]; 42 | $this->version = $parts[1]; 43 | } 44 | 45 | $this->validateName(); 46 | $this->locator = new PackageFileLocator($this); 47 | } 48 | 49 | /** 50 | * Check to see if the current package has already been installed. 51 | * @return bool 52 | */ 53 | public function isInstalled() 54 | { 55 | $packageInstalled = true; 56 | try { 57 | $this->validatePath(); 58 | } catch (PackageDirectoryNotFoundException $e) { 59 | $packageInstalled = false; 60 | } 61 | 62 | return $packageInstalled; 63 | } 64 | 65 | /** 66 | * Validates a package name, expecting format "vendorname/packagename". 67 | * @throws \LaravelPackageManager\Exceptions\InvalidPackageNameException 68 | * @return bool 69 | */ 70 | protected function validateName() 71 | { 72 | $packageName = $this->getName(); 73 | if (preg_match('/[a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+/', $packageName) == 0) { 74 | throw new InvalidPackageNameException("Invalid package name: $packageName"); 75 | return false; 76 | } 77 | 78 | return true; 79 | } 80 | 81 | /** 82 | * Validate the path the package is expected to reside in. 83 | * @throws \LaravelPackageManager\Exceptions\PackageDirectoryNotFoundException 84 | * @return bool 85 | */ 86 | public function validatePath() 87 | { 88 | $path = $this->getPath(); 89 | if (! is_dir($path)) { 90 | throw new PackageDirectoryNotFoundException("Package directory not found: $path"); 91 | return false; 92 | } 93 | 94 | return true; 95 | } 96 | 97 | /** 98 | * The package name. 99 | * @return string 100 | */ 101 | public function getName() 102 | { 103 | return $this->name; 104 | } 105 | 106 | /** 107 | * Get the specified version of the package to install. Returns empty string if no version was explicitly specified. 108 | * @return string 109 | */ 110 | public function getVersion() 111 | { 112 | return is_null($this->version) ? '' : $this->version; 113 | } 114 | 115 | /** 116 | * Returns the absolute path where this package will be installed. 117 | * @return string 118 | */ 119 | public function getPath() 120 | { 121 | return base_path().'/vendor/'.$this->getName(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Packages/PackageInstaller.php: -------------------------------------------------------------------------------- 1 | findComposerBinary().' require '.$package->getName(); 17 | if ($package->getVersion()) { 18 | $cmd .= ':'.$package->getVersion(); 19 | } 20 | if ($InstallAsDev) { 21 | $cmd .= ' --dev'; 22 | } 23 | 24 | $runner = new RunExternalCommand($cmd); 25 | try { 26 | $runner->run(); 27 | } catch (Exception $e) { 28 | echo 'Error: '.$e->getMessage().PHP_EOL; 29 | } 30 | } 31 | 32 | /** 33 | * Get the composer command for the environment. 34 | * 35 | * @return string 36 | */ 37 | protected function findComposerBinary() 38 | { 39 | if (file_exists(base_path().'/composer.phar')) { 40 | return '"'.PHP_BINARY.'" composer.phar'; 41 | } 42 | 43 | return 'composer'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Packages/PackageRegistration.php: -------------------------------------------------------------------------------- 1 | command = $command; 18 | $this->prompt = $prompt; 19 | $this->config = new ConfigurationFile('app'); 20 | } 21 | 22 | /** 23 | * Register a facade or service provider within a package. 24 | * @param Package $package 25 | * @param ItemInformation $info 26 | */ 27 | public function register(Package $package, ItemInformation $info) 28 | { 29 | $package = $package; 30 | $searchLine = null; 31 | switch ($info->type) { 32 | case ItemInformation::FACADE: 33 | $searchLine = "'aliases' => ["; 34 | 35 | case ItemInformation::SERVICE_PROVIDER: 36 | if (strlen($searchLine) == 0) { 37 | $searchLine = 'LaravelPackageManager\\LaravelPackageManagerServiceProvider::class,'; 38 | } 39 | 40 | $install = $this->prompt->promptToInstall($info); 41 | break; 42 | 43 | default: 44 | $this->command->comment('Unknown package type. This is probably not a Laravel package.'); 45 | 46 | return false; 47 | } 48 | 49 | if ($install) { 50 | $regline = $this->generateRegistrationLine($info); 51 | 52 | $config = $this->config->read(); 53 | if (strpos($config, $regline) !== false) { 54 | $this->command->comment($info->displayName()."'".$info->name."' is already registered."); 55 | 56 | return true; 57 | } 58 | 59 | $count = 0; 60 | $config = str_replace($searchLine, $searchLine.PHP_EOL." $regline", $config, $count); 61 | 62 | if ($count > 0) { 63 | $this->config->write($config); 64 | 65 | return true; 66 | } 67 | } 68 | 69 | return false; 70 | } 71 | 72 | public function unregister(Package $package, ItemInformation $info) 73 | { 74 | $package = $package; 75 | $searchLine = null; 76 | switch ($info->type) { 77 | case ItemInformation::FACADE: 78 | case ItemInformation::SERVICE_PROVIDER: 79 | $unregister = $this->prompt->promptToUnregister($info); 80 | break; 81 | 82 | default: 83 | $this->command->comment('Unknown package type. This is probably not a Laravel package.'); 84 | 85 | return false; 86 | } 87 | 88 | if ($unregister) { 89 | $regline = $this->generateRegistrationLine($info); 90 | 91 | $config = $this->config->read(); 92 | if (strpos($config, $regline) === false) { 93 | $this->command->comment($info->displayName()."'".$info->name."' is not registered."); 94 | 95 | return false; 96 | } 97 | 98 | $count = 0; 99 | $config = str_replace($regline, '', $config, $count); 100 | 101 | if ($count > 0) { 102 | $this->config->write($config); 103 | 104 | return true; 105 | } 106 | } 107 | 108 | return false; 109 | } 110 | 111 | /** 112 | * Register mutliple facades/service providers. 113 | * @param Package $package 114 | * @param array $infos 115 | */ 116 | public function registerAll(Package $package, array $infos) 117 | { 118 | $result = true; 119 | foreach ($infos as $info) { 120 | if (! $this->register($package, $info)) { 121 | $result = false; 122 | } 123 | } 124 | 125 | return $result; 126 | } 127 | 128 | /** 129 | * Unregister mutliple facades/service providers. 130 | * @param Package $package 131 | * @param array $infos 132 | */ 133 | public function unregisterAll(Package $package, array $infos) 134 | { 135 | $result = true; 136 | foreach ($infos as $info) { 137 | if (! $this->unregister($package, $info)) { 138 | $result = false; 139 | } 140 | } 141 | 142 | return $result; 143 | } 144 | 145 | /** 146 | * Generate the code needed to register the service provider or facade. 147 | * 148 | * @param ItemInformation $item 149 | * @return string 150 | */ 151 | protected function generateRegistrationLine(ItemInformation $item) 152 | { 153 | switch ($item->type) { 154 | case ItemInformation::SERVICE_PROVIDER: 155 | return $item->namespace.'\\'.$item->classname.'::class,'; 156 | 157 | case ItemInformation::FACADE: 158 | return "'".(strlen($item->name) > 0 ? $item->name : $item->classname)."' => ".$item->namespace.'\\'.$item->classname.'::class,'; 159 | 160 | default: 161 | return ''; 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/Packages/PackageRequirer.php: -------------------------------------------------------------------------------- 1 | command = $command; 37 | $this->output = new Output($this->command); 38 | $this->userPrompt = new UserPrompt($this->output); 39 | $this->options = new CommandOptions([]); 40 | } 41 | 42 | /** 43 | * Install a package, and register any service providers and/or facades it provides. 44 | * @param string $packageName 45 | * @param array $options 46 | */ 47 | public function require($packageName, array $options) 48 | { 49 | $package = new Package($packageName); 50 | if (! $package->isInstalled() || ! $options['register-only']) { // (is_null($options['register-only']) || $options['register-only'] == false)) { 51 | $installer = new PackageInstaller; 52 | $installer->install($package, $options['dev']); // in_array('dev', $options)); 53 | } 54 | 55 | $locator = new PackageFileLocator($package); 56 | $locator->locateFiles(); 57 | 58 | $classifier = new PackageFileClassifier(); 59 | $classifier->classifyFiles($locator->getFiles()); 60 | 61 | $registrar = new PackageRegistration($this->command, $this->userPrompt); 62 | $registrar->registerAll($package, $classifier->getFiles()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Support/CommandOptions.php: -------------------------------------------------------------------------------- 1 | 0) { 12 | $this->options = $options; 13 | } 14 | } 15 | 16 | /** 17 | * Add an option to the list. 18 | * @param string $name 19 | */ 20 | public function add($name) 21 | { 22 | array_push($this->options, $name); 23 | } 24 | 25 | /** 26 | * Remove an option. 27 | * @param string $name 28 | */ 29 | public function remove($name) 30 | { 31 | unset($this->options[$name]); 32 | } 33 | 34 | /** 35 | * Remove all items from the list. 36 | */ 37 | public function clear() 38 | { 39 | $this->options = []; 40 | } 41 | 42 | /** 43 | * Check if $name exists in options. 44 | * @param string $name 45 | * @return bool 46 | */ 47 | public function hasOption($name) 48 | { 49 | return $this->has($name); 50 | } 51 | 52 | /** 53 | * Get the value of option $name. 54 | * @param string $name 55 | * @return mixed 56 | */ 57 | public function option($name) 58 | { 59 | return $this->get($name); 60 | } 61 | 62 | /** 63 | * Check to see if $name exists in options. 64 | * @param unknown $name 65 | * @return bool 66 | */ 67 | public function has($name) 68 | { 69 | return in_array($name, $this->options); 70 | } 71 | 72 | /** 73 | * Get the value of option $name. 74 | * @param unknown $name 75 | * @return mixed 76 | */ 77 | public function get($name) 78 | { 79 | if (is_null($this->options[$name])) { 80 | return false; 81 | } 82 | 83 | return $this->options[$name]; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Support/ConfigurationFile.php: -------------------------------------------------------------------------------- 1 | name = trim($name); 22 | $this->validateFilename(); 23 | } 24 | 25 | /** 26 | * Validate the filename generated from the $name provided. 27 | * @throws \LaravelPackageManager\Exceptions\ConfigurationFileNotFoundException 28 | * @return bool 29 | */ 30 | protected function validateFilename() 31 | { 32 | $filename = $this->filename(); 33 | 34 | if (! file_exists($filename)) { 35 | throw ConfigurationFileNotFoundException('File not found: '.$filename); 36 | } 37 | return true; 38 | } 39 | 40 | /** 41 | * Returns the configuration filename. 42 | * @return string 43 | */ 44 | public function filename() 45 | { 46 | return config_path().'/'.$this->name.'.php'; 47 | } 48 | 49 | /** 50 | * Read an existing configuration file. 51 | * @throws \LaravelPackageManager\Exceptions\ConfigurationFileNotFoundException 52 | * @return string 53 | */ 54 | public function read() 55 | { 56 | return file_get_contents($this->filename()); 57 | } 58 | 59 | /** 60 | * Write contents to a configuration file. 61 | * @param string $contents 62 | * @return string 63 | */ 64 | public function write($contents) 65 | { 66 | return file_put_contents($this->filename(), $contents); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Support/FacadeClassLoader.php: -------------------------------------------------------------------------------- 1 | getLoaderClassname(); 43 | $loaderfn = $this->getLoaderFilename($loaderClassname); 44 | $contents = $this->processFacadeFileContents($loaderClassname, $contents); 45 | 46 | $this->writeDataToTempFile($loaderfn, $contents); 47 | 48 | try { 49 | include_once $loaderfn; 50 | $facadeName = $loaderClassname::getFacadeName(); 51 | } catch (\Exception $e) { 52 | return false; 53 | } finally { 54 | $this->cleanup(); 55 | } 56 | 57 | return ucfirst($facadeName); 58 | } 59 | 60 | /** 61 | * Refreshes the class loader. Required when using the same class loader multiple times, 62 | * to avoid returning the wrong classname. If you are repeatedly getting the same 63 | * classname for different Facade files, call refresh(). 64 | * @return \LaravelPackageManager\Support\FacadeClassLoader 65 | */ 66 | public function refresh() 67 | { 68 | $this->getLoaderClassname(true); 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * Write data to a temp file. 75 | * @param string $filename 76 | * @param string $data 77 | * @return \LaravelPackageManager\Support\FacadeClassLoader 78 | */ 79 | protected function writeDataToTempFile($filename, $data) 80 | { 81 | if (! in_array($filename, $this->tempFiles)) { 82 | $this->tempFiles[] = $filename; 83 | } 84 | file_put_contents($filename, $data); 85 | 86 | return $this; 87 | } 88 | 89 | /** 90 | * Process the contents of the original Facade class, removing namespace & 91 | * uses. Renames the existing classname with a dynamic 92 | * one. Makes all methods public, rename getFacadeAccessor() 93 | * to getFacadeName(). 94 | * @param string $loaderClassname 95 | * @param string $contents 96 | * @return string 97 | */ 98 | protected function processFacadeFileContents($loaderClassname, $contents) 99 | { 100 | $temp = $contents; 101 | $temp = str_replace('namespace ', '//namespace ', $temp); 102 | $temp = preg_replace('/use /', '//use ', $temp); 103 | $temp = preg_replace('/class\s+([a-zA-Z0-9_]+)\s+extends\s+[\\\A-Za-z0-9_]*Facade/', "class $loaderClassname ", $temp); 104 | $temp = preg_replace('/(protected|private)/', 'public', $temp); 105 | $temp = preg_replace('/getFacadeAccessor/', 'getFacadeName', $temp); 106 | //$temp = preg_replace('/protected\s+static\s+function\s+getFacadeAccessor\b/', 'public static function getFacadeName', $temp); 107 | 108 | return $temp; 109 | } 110 | 111 | /** 112 | * Stores the dynamically generated temp classname for use with loading 113 | * the Facade code. 114 | * @param string $refresh 115 | * @return string 116 | */ 117 | protected function getLoaderClassname($refresh = false) 118 | { 119 | static $classname = ''; 120 | if ($classname == '' || $refresh === true) { 121 | $classname = (new \ReflectionClass($this))->getShortName().sha1(mt_rand(1, 999999999)); 122 | } 123 | 124 | return $classname; 125 | } 126 | 127 | /** 128 | * Return the dynamically generated filename for loading the Facade code. 129 | * @param string $classname 130 | * @return string 131 | */ 132 | protected function getLoaderFilename($classname) 133 | { 134 | return $classname.'.laravel-require.facade-loader.php'; 135 | } 136 | 137 | /** 138 | * Remove the temp file that was used to load the facade code. 139 | * @return \LaravelRequire\Support\FacadeClassLoader 140 | */ 141 | protected function cleanup() 142 | { 143 | foreach ($this->tempFiles as $file) { 144 | if (file_exists($file)) { 145 | unlink($file); 146 | } 147 | } 148 | $this->tempFiles = []; 149 | 150 | return $this; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Support/ItemInformation.php: -------------------------------------------------------------------------------- 1 | type = $type; 29 | } 30 | 31 | /** 32 | * Convert a type into a string. 33 | * @return string 34 | */ 35 | public function displayName() 36 | { 37 | switch ($this->type) { 38 | case self::SERVICE_PROVIDER: 39 | return 'Service Provier'; 40 | case self::FACADE: 41 | return 'Facade'; 42 | default: 43 | return 'Unknown'; 44 | } 45 | } 46 | 47 | /** 48 | * Get the fully-qualified classname. 49 | * 50 | * @return string 51 | */ 52 | public function qualifiedName() 53 | { 54 | return $this->namespace.'\\'.$this->classname; 55 | } 56 | 57 | public function __call($name, $params) 58 | { 59 | if (isset($this->$name)) { 60 | if (count($params) > 0) { 61 | $this->$name = $params[0]; 62 | 63 | return $this; 64 | } 65 | 66 | return $this->$name; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Support/Output.php: -------------------------------------------------------------------------------- 1 | command = $command; 17 | } 18 | 19 | /** 20 | * Handle calls to various Command methods. 21 | * @param string $name 22 | * @param array $params 23 | * @return mixed 24 | */ 25 | public function __call($name, $params) 26 | { 27 | if (in_array($name, ['info', 'comment', 'line', 'error', 'confirm'])) { 28 | return $this->command->$name($params[0]); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Support/RegEx.php: -------------------------------------------------------------------------------- 1 | isBasicCapture($filter) && strpos($filter, '|') !== false; 28 | } 29 | 30 | /** 31 | * Strip off the leading and trailing characters from $filter. 32 | * TODO this is too similar to getFilterExpressionOnly, consider merging into one method. 33 | * @param string $filter 34 | * @return string 35 | */ 36 | protected function removeBasicCaptureChars($filter) 37 | { 38 | return substr($filter, 1, strlen($filter) - 2); 39 | } 40 | 41 | /** 42 | * Compiles an array of regular expressions into one expression, 43 | * to avoid multiple calls trying to match each individual 44 | * expression. 45 | * 46 | * @param array $filters 47 | * @return string 48 | */ 49 | public function compileFiltersToRegEx(array $filters) 50 | { 51 | $parts = []; 52 | 53 | foreach ($filters as $filter) { 54 | $expression = $this->getFilterExpressionOnly($filter); 55 | 56 | if ($this->isBasicCaptureWithPipes($expression)) { 57 | $expression = $this->removeBasicCaptureChars($expression); 58 | } 59 | $parts[] = $expression; 60 | } 61 | $result = '/('.implode('|', $parts).')/'; 62 | 63 | return $result; 64 | } 65 | 66 | /** 67 | * Extract the regular expression, removing the leading and trailing '/' chars. 68 | * If $filter isn't a regex, just return the value of $filter unmodified. 69 | * @param string $filter 70 | * @return string 71 | */ 72 | protected function getFilterExpressionOnly($filter) 73 | { 74 | if (! $this->isRegularExpression($filter)) { 75 | return $filter; 76 | } 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 bool 86 | */ 87 | public function isRegularExpression($filter) 88 | { 89 | if (strlen($filter) <= 2) { 90 | return false; 91 | } 92 | 93 | $firstchar = $filter[0]; 94 | $lastchar = $filter[strlen($filter) - 1]; 95 | 96 | return ($firstchar == '/') && ($firstchar == $lastchar); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Support/RunExternalCommand.php: -------------------------------------------------------------------------------- 1 | command = $command; 15 | $this->validateCommand(); 16 | } 17 | 18 | public function run() 19 | { 20 | $process = new Process($this->command, base_path(), null, null, null); 21 | 22 | if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { 23 | $process->setTty(true); 24 | } 25 | 26 | $process->run(function ($type, $line) { 27 | $type = $type; 28 | $this->line($line); 29 | }); 30 | } 31 | 32 | protected function validateCommand() 33 | { 34 | if (! is_string($this->command) || strlen(trim($this->command)) == 0) { 35 | throw new InvalidExternalCommandException('Invalid command string.'); 36 | } 37 | return true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Support/UserPrompt.php: -------------------------------------------------------------------------------- 1 | output = $output; 20 | } 21 | 22 | /** 23 | * Ask the user if the specified item should be installed. 24 | * @param ItemInformation $info 25 | * @return void 26 | */ 27 | public function promptToInstall(ItemInformation $info) 28 | { 29 | switch ($info->type) { 30 | case ItemInformation::FACADE: 31 | case ItemInformation::SERVICE_PROVIDER: 32 | if ($this->output->confirm('Register '.$info->displayName()." '".$info->name."'?")) { 33 | $this->output->info('Registering '.$info->displayName().'...'); 34 | 35 | return true; 36 | } 37 | $this->output->comment('Skipping registration of '.$info->displayName().' \''.$info->name.'\'.'); 38 | } 39 | 40 | return false; 41 | } 42 | 43 | /** 44 | * Ask the user if they want to unregister a service provider or facade. 45 | * @param \LaravelPackageManager\Support\ItemInformation $info 46 | * @return bool 47 | */ 48 | public function promptToUnregister(ItemInformation $info) 49 | { 50 | switch ($info->type) { 51 | case ItemInformation::FACADE: 52 | case ItemInformation::SERVICE_PROVIDER: 53 | if ($this->output->confirm('Unregister '.$info->displayName()." '".$info->name."'?")) { 54 | $this->output->info('Unregistering '.$info->displayName().'...'); 55 | 56 | return true; 57 | } 58 | $this->output->comment('Skipping unregistration of '.$info->displayName().' \''.$info->name.'\'.'); 59 | } 60 | 61 | return false; 62 | } 63 | } 64 | --------------------------------------------------------------------------------