├── 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 |

2 |
3 |
4 |
5 |
6 |
7 |
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 |
--------------------------------------------------------------------------------