├── LICENSE.md ├── README.md ├── composer.json ├── config └── tinker.php └── src ├── ClassAliasAutoloader.php ├── Console └── TinkerCommand.php ├── TinkerCaster.php └── TinkerServiceProvider.php /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Taylor Otwell 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 |

Logo Laravel Tinker

2 | 3 |

4 | Build Status 5 | Total Downloads 6 | Latest Stable Version 7 | License 8 |

9 | 10 | ## Introduction 11 | 12 | Laravel Tinker is a powerful REPL for the Laravel framework. 13 | 14 | ## Official Documentation 15 | 16 | Documentation for Tinker can be found on the [Laravel website](https://laravel.com/docs/artisan#tinker). 17 | 18 | ## Contributing 19 | 20 | Thank you for considering contributing to Tinker! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). 21 | 22 | ## Code of Conduct 23 | 24 | In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). 25 | 26 | ## Security Vulnerabilities 27 | 28 | Please review [our security policy](https://github.com/laravel/tinker/security/policy) on how to report security vulnerabilities. 29 | 30 | ## License 31 | 32 | Laravel Tinker is open-sourced software licensed under the [MIT license](LICENSE.md). 33 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/tinker", 3 | "description": "Powerful REPL for the Laravel framework.", 4 | "keywords": ["tinker", "repl", "psysh", "laravel"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Taylor Otwell", 9 | "email": "taylor@laravel.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^7.2.5|^8.0", 14 | "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 15 | "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 16 | "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 17 | "psy/psysh": "^0.11.1|^0.12.0", 18 | "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0" 19 | }, 20 | "require-dev": { 21 | "mockery/mockery": "~1.3.3|^1.4.2", 22 | "phpstan/phpstan": "^1.10", 23 | "phpunit/phpunit": "^8.5.8|^9.3.3|^10.0" 24 | }, 25 | "suggest": { 26 | "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0)." 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Laravel\\Tinker\\": "src/" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Laravel\\Tinker\\Tests\\": "tests/", 36 | "App\\": "tests/fixtures/app", 37 | "One\\Two\\": "tests/fixtures/vendor/one/two" 38 | } 39 | }, 40 | "extra": { 41 | "laravel": { 42 | "providers": [ 43 | "Laravel\\Tinker\\TinkerServiceProvider" 44 | ] 45 | } 46 | }, 47 | "config": { 48 | "sort-packages": true 49 | }, 50 | "minimum-stability": "dev", 51 | "prefer-stable": true 52 | } 53 | -------------------------------------------------------------------------------- /config/tinker.php: -------------------------------------------------------------------------------- 1 | [ 17 | // App\Console\Commands\ExampleCommand::class, 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Auto Aliased Classes 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Tinker will not automatically alias classes in your vendor namespaces 26 | | but you may explicitly allow a subset of classes to get aliased by 27 | | adding the names of each of those classes to the following list. 28 | | 29 | */ 30 | 31 | 'alias' => [ 32 | // 33 | ], 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Classes That Should Not Be Aliased 38 | |-------------------------------------------------------------------------- 39 | | 40 | | Typically, Tinker automatically aliases classes as you require them in 41 | | Tinker. However, you may wish to never alias certain classes, which 42 | | you may accomplish by listing the classes in the following array. 43 | | 44 | */ 45 | 46 | 'dont_alias' => [ 47 | 'App\Nova', 48 | ], 49 | 50 | ]; 51 | -------------------------------------------------------------------------------- /src/ClassAliasAutoloader.php: -------------------------------------------------------------------------------- 1 | shell = $shell; 73 | $this->vendorPath = dirname(dirname($classMapPath)); 74 | $this->includedAliases = collect($includedAliases); 75 | $this->excludedAliases = collect($excludedAliases); 76 | 77 | $classes = require $classMapPath; 78 | 79 | foreach ($classes as $class => $path) { 80 | if (! $this->isAliasable($class, $path)) { 81 | continue; 82 | } 83 | 84 | $name = class_basename($class); 85 | 86 | if (! isset($this->classes[$name])) { 87 | $this->classes[$name] = $class; 88 | } 89 | } 90 | } 91 | 92 | /** 93 | * Find the closest class by name. 94 | * 95 | * @param string $class 96 | * @return void 97 | */ 98 | public function aliasClass($class) 99 | { 100 | if (Str::contains($class, '\\')) { 101 | return; 102 | } 103 | 104 | $fullName = $this->classes[$class] ?? false; 105 | 106 | if ($fullName) { 107 | $this->shell->writeStdout("[!] Aliasing '{$class}' to '{$fullName}' for this Tinker session.\n"); 108 | 109 | class_alias($fullName, $class); 110 | } 111 | } 112 | 113 | /** 114 | * Unregister the alias loader instance. 115 | * 116 | * @return void 117 | */ 118 | public function unregister() 119 | { 120 | spl_autoload_unregister([$this, 'aliasClass']); 121 | } 122 | 123 | /** 124 | * Handle the destruction of the instance. 125 | * 126 | * @return void 127 | */ 128 | public function __destruct() 129 | { 130 | $this->unregister(); 131 | } 132 | 133 | /** 134 | * Whether a class may be aliased. 135 | * 136 | * @param string $class 137 | * @param string $path 138 | */ 139 | public function isAliasable($class, $path) 140 | { 141 | if (! Str::contains($class, '\\')) { 142 | return false; 143 | } 144 | 145 | if (! $this->includedAliases->filter(function ($alias) use ($class) { 146 | return Str::startsWith($class, $alias); 147 | })->isEmpty()) { 148 | return true; 149 | } 150 | 151 | if (Str::startsWith($path, $this->vendorPath)) { 152 | return false; 153 | } 154 | 155 | if (! $this->excludedAliases->filter(function ($alias) use ($class) { 156 | return Str::startsWith($class, $alias); 157 | })->isEmpty()) { 158 | return false; 159 | } 160 | 161 | return true; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/Console/TinkerCommand.php: -------------------------------------------------------------------------------- 1 | getApplication()->setCatchExceptions(false); 47 | 48 | $config = Configuration::fromInput($this->input); 49 | $config->setUpdateCheck(Checker::NEVER); 50 | 51 | $config->getPresenter()->addCasters( 52 | $this->getCasters() 53 | ); 54 | 55 | if ($this->option('execute')) { 56 | $config->setRawOutput(true); 57 | } 58 | 59 | $shell = new Shell($config); 60 | $shell->addCommands($this->getCommands()); 61 | $shell->setIncludes($this->argument('include')); 62 | 63 | $path = Env::get('COMPOSER_VENDOR_DIR', $this->getLaravel()->basePath().DIRECTORY_SEPARATOR.'vendor'); 64 | 65 | $path .= '/composer/autoload_classmap.php'; 66 | 67 | $config = $this->getLaravel()->make('config'); 68 | 69 | $loader = ClassAliasAutoloader::register( 70 | $shell, $path, $config->get('tinker.alias', []), $config->get('tinker.dont_alias', []) 71 | ); 72 | 73 | if ($code = $this->option('execute')) { 74 | try { 75 | $shell->setOutput($this->output); 76 | $shell->execute($code); 77 | } finally { 78 | $loader->unregister(); 79 | } 80 | 81 | return 0; 82 | } 83 | 84 | try { 85 | return $shell->run(); 86 | } finally { 87 | $loader->unregister(); 88 | } 89 | } 90 | 91 | /** 92 | * Get artisan commands to pass through to PsySH. 93 | * 94 | * @return array 95 | */ 96 | protected function getCommands() 97 | { 98 | $commands = []; 99 | 100 | foreach ($this->getApplication()->all() as $name => $command) { 101 | if (in_array($name, $this->commandWhitelist)) { 102 | $commands[] = $command; 103 | } 104 | } 105 | 106 | $config = $this->getLaravel()->make('config'); 107 | 108 | foreach ($config->get('tinker.commands', []) as $command) { 109 | $commands[] = $this->getApplication()->add( 110 | $this->getLaravel()->make($command) 111 | ); 112 | } 113 | 114 | return $commands; 115 | } 116 | 117 | /** 118 | * Get an array of Laravel tailored casters. 119 | * 120 | * @return array 121 | */ 122 | protected function getCasters() 123 | { 124 | $casters = [ 125 | 'Illuminate\Support\Collection' => 'Laravel\Tinker\TinkerCaster::castCollection', 126 | 'Illuminate\Support\HtmlString' => 'Laravel\Tinker\TinkerCaster::castHtmlString', 127 | 'Illuminate\Support\Stringable' => 'Laravel\Tinker\TinkerCaster::castStringable', 128 | ]; 129 | 130 | if (class_exists('Illuminate\Database\Eloquent\Model')) { 131 | $casters['Illuminate\Database\Eloquent\Model'] = 'Laravel\Tinker\TinkerCaster::castModel'; 132 | } 133 | 134 | if (class_exists('Illuminate\Process\ProcessResult')) { 135 | $casters['Illuminate\Process\ProcessResult'] = 'Laravel\Tinker\TinkerCaster::castProcessResult'; 136 | } 137 | 138 | if (class_exists('Illuminate\Foundation\Application')) { 139 | $casters['Illuminate\Foundation\Application'] = 'Laravel\Tinker\TinkerCaster::castApplication'; 140 | } 141 | 142 | $config = $this->getLaravel()->make('config'); 143 | 144 | return array_merge($casters, (array) $config->get('tinker.casters', [])); 145 | } 146 | 147 | /** 148 | * Get the console command arguments. 149 | * 150 | * @return array 151 | */ 152 | protected function getArguments() 153 | { 154 | return [ 155 | ['include', InputArgument::IS_ARRAY, 'Include file(s) before starting tinker'], 156 | ]; 157 | } 158 | 159 | /** 160 | * Get the console command options. 161 | * 162 | * @return array 163 | */ 164 | protected function getOptions() 165 | { 166 | return [ 167 | ['execute', null, InputOption::VALUE_OPTIONAL, 'Execute the given code using Tinker'], 168 | ]; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/TinkerCaster.php: -------------------------------------------------------------------------------- 1 | $property(); 46 | 47 | if (! is_null($val)) { 48 | $results[Caster::PREFIX_VIRTUAL.$property] = $val; 49 | } 50 | } catch (Exception $e) { 51 | // 52 | } 53 | } 54 | 55 | return $results; 56 | } 57 | 58 | /** 59 | * Get an array representing the properties of a collection. 60 | * 61 | * @param \Illuminate\Support\Collection $collection 62 | * @return array 63 | */ 64 | public static function castCollection($collection) 65 | { 66 | return [ 67 | Caster::PREFIX_VIRTUAL.'all' => $collection->all(), 68 | ]; 69 | } 70 | 71 | /** 72 | * Get an array representing the properties of an html string. 73 | * 74 | * @param \Illuminate\Support\HtmlString $htmlString 75 | * @return array 76 | */ 77 | public static function castHtmlString($htmlString) 78 | { 79 | return [ 80 | Caster::PREFIX_VIRTUAL.'html' => $htmlString->toHtml(), 81 | ]; 82 | } 83 | 84 | /** 85 | * Get an array representing the properties of a fluent string. 86 | * 87 | * @param \Illuminate\Support\Stringable $stringable 88 | * @return array 89 | */ 90 | public static function castStringable($stringable) 91 | { 92 | return [ 93 | Caster::PREFIX_VIRTUAL.'value' => (string) $stringable, 94 | ]; 95 | } 96 | 97 | /** 98 | * Get an array representing the properties of a process result. 99 | * 100 | * @param \Illuminate\Process\ProcessResult $result 101 | * @return array 102 | */ 103 | public static function castProcessResult($result) 104 | { 105 | return [ 106 | Caster::PREFIX_VIRTUAL.'output' => $result->output(), 107 | Caster::PREFIX_VIRTUAL.'errorOutput' => $result->errorOutput(), 108 | Caster::PREFIX_VIRTUAL.'exitCode' => $result->exitCode(), 109 | Caster::PREFIX_VIRTUAL.'successful' => $result->successful(), 110 | ]; 111 | } 112 | 113 | /** 114 | * Get an array representing the properties of a model. 115 | * 116 | * @param \Illuminate\Database\Eloquent\Model $model 117 | * @return array 118 | */ 119 | public static function castModel($model) 120 | { 121 | $attributes = array_merge( 122 | $model->getAttributes(), $model->getRelations() 123 | ); 124 | 125 | $visible = array_flip( 126 | $model->getVisible() ?: array_diff(array_keys($attributes), $model->getHidden()) 127 | ); 128 | 129 | $hidden = array_flip($model->getHidden()); 130 | 131 | $appends = (function () { 132 | return array_combine($this->appends, $this->appends); // @phpstan-ignore-line 133 | })->bindTo($model, $model)(); 134 | 135 | foreach ($appends as $appended) { 136 | $attributes[$appended] = $model->{$appended}; 137 | } 138 | 139 | $results = []; 140 | 141 | foreach ($attributes as $key => $value) { 142 | $prefix = ''; 143 | 144 | if (isset($visible[$key])) { 145 | $prefix = Caster::PREFIX_VIRTUAL; 146 | } 147 | 148 | if (isset($hidden[$key])) { 149 | $prefix = Caster::PREFIX_PROTECTED; 150 | } 151 | 152 | $results[$prefix.$key] = $value; 153 | } 154 | 155 | return $results; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/TinkerServiceProvider.php: -------------------------------------------------------------------------------- 1 | app instanceof LaravelApplication && $this->app->runningInConsole()) { 23 | $this->publishes([$source => $this->app->configPath('tinker.php')]); 24 | } elseif ($this->app instanceof LumenApplication) { 25 | $this->app->configure('tinker'); 26 | } 27 | 28 | $this->mergeConfigFrom($source, 'tinker'); 29 | } 30 | 31 | /** 32 | * Register the service provider. 33 | * 34 | * @return void 35 | */ 36 | public function register() 37 | { 38 | $this->app->singleton('command.tinker', function () { 39 | return new TinkerCommand; 40 | }); 41 | 42 | $this->commands(['command.tinker']); 43 | } 44 | 45 | /** 46 | * Get the services provided by the provider. 47 | * 48 | * @return array 49 | */ 50 | public function provides() 51 | { 52 | return ['command.tinker']; 53 | } 54 | } 55 | --------------------------------------------------------------------------------