├── .github └── workflows │ ├── php-cs-fixer.yml │ └── run-tests.yml ├── .php-cs-fixer.dist.php ├── .prettierrc ├── composer.json ├── config └── tinker-on-vscode.php ├── images ├── demo3.png ├── demo4.png └── demo5.png └── src ├── ExecuteCodeCommand.php ├── TinkerOnVscodeCommand.php └── TinkerOnVscodeServiceProvider.php /.github/workflows/php-cs-fixer.yml: -------------------------------------------------------------------------------- 1 | name: Check & fix styling 2 | 3 | on: [push] 4 | 5 | jobs: 6 | php-cs-fixer: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v2 12 | with: 13 | ref: ${{ github.head_ref }} 14 | 15 | - name: Run PHP CS Fixer 16 | uses: docker://oskarstark/php-cs-fixer-ga 17 | with: 18 | args: --config=.php-cs-fixer.dist.php --allow-risky=yes 19 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | fail-fast: true 10 | matrix: 11 | os: [ubuntu-latest] 12 | php: [8.0] 13 | dependency-version: [prefer-lowest, prefer-stable] 14 | 15 | name: P${{ matrix.php }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v2 20 | 21 | - name: Cache dependencies 22 | uses: actions/cache@v2 23 | with: 24 | path: ~/.composer/cache/files 25 | key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 26 | 27 | - name: Setup PHP 28 | uses: shivammathur/setup-php@v2 29 | with: 30 | php-version: ${{ matrix.php }} 31 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick 32 | coverage: none 33 | 34 | - name: Install dependencies 35 | run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction 36 | 37 | - name: Execute tests 38 | run: vendor/bin/phpunit 39 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__.'/src', 6 | __DIR__.'/tests', 7 | ]) 8 | ->name('*.php') 9 | ->notName('*.blade.php'); 10 | 11 | return (new PhpCsFixer\Config()) 12 | ->setUsingCache(false) 13 | ->setRiskyAllowed(true) 14 | ->setRules([ 15 | '@PSR12' => true, 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 18 | 'no_unused_imports' => true, 19 | 'not_operator_with_successor_space' => true, 20 | 'trailing_comma_in_multiline' => true, 21 | 'phpdoc_scalar' => true, 22 | 'unary_operator_spaces' => true, 23 | 'binary_operator_spaces' => true, 24 | 'blank_line_before_statement' => [ 25 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 26 | ], 27 | 'phpdoc_single_line_var_spacing' => true, 28 | 'phpdoc_var_without_name' => true, 29 | 'class_attributes_separation' => [ 30 | 'elements' => [ 31 | 'method' => 'one', 32 | ], 33 | ], 34 | 'method_argument_space' => [ 35 | 'on_multiline' => 'ensure_fully_multiline', 36 | 'keep_multiple_spaces_after_comma' => true, 37 | ], 38 | 'no_extra_blank_lines' => [ 39 | 'tokens' => [ 40 | 'extra', 41 | 'curly_brace_block', 42 | ], 43 | ], 44 | 'no_singleline_whitespace_before_semicolons' => true, 45 | 'concat_space' => true, 46 | 'no_whitespace_before_comma_in_array' => true, 47 | 'array_indentation' => true, 48 | 'method_chaining_indentation' => true, 49 | 'object_operator_without_whitespace' => true, 50 | 'trim_array_spaces' => true, 51 | 'whitespace_after_comma_in_array' => [ 52 | 'ensure_single_space' => true, 53 | ], 54 | 'php_unit_method_casing' => ['case' => 'snake_case'], 55 | 'single_line_empty_body' => true, 56 | 'single_space_around_construct' => true, 57 | 'no_trailing_comma_in_singleline' => true, 58 | ]) 59 | ->setFinder($finder); 60 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 4, 5 | "trailingComma": "all", 6 | "htmlWhitespaceSensitivity": "css", 7 | "printWidth": 140 8 | } 9 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pkboom/laravel-tinker-on-vscode", 3 | "description": "Tinker on vscode", 4 | "keywords": [ 5 | "pkboom", 6 | "tinker", 7 | "vscode" 8 | ], 9 | "homepage": "https://github.com/pkboom/laravel-tinker-on-vscode", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Keunbae Park", 14 | "email": "ppotpo@gmail.com" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.1", 19 | "illuminate/console": "^10.0", 20 | "illuminate/http": "^10.0", 21 | "illuminate/support": "^10.0", 22 | "laravel/tinker": "^2.10.1", 23 | "pkboom/file-watcher": "^0.2.6", 24 | "react/event-loop": "^1.5", 25 | "symfony/console": "^6.2.0" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Pkboom\\TinkerOnVscode\\": "src" 30 | } 31 | }, 32 | "scripts": { 33 | "test": "vendor/bin/phpunit" 34 | }, 35 | "config": { 36 | "sort-packages": true 37 | }, 38 | "extra": { 39 | "laravel": { 40 | "providers": [ 41 | "Pkboom\\TinkerOnVscode\\TinkerOnVscodeServiceProvider" 42 | ] 43 | } 44 | }, 45 | "minimum-stability": "dev", 46 | "prefer-stable": true 47 | } 48 | -------------------------------------------------------------------------------- /config/tinker-on-vscode.php: -------------------------------------------------------------------------------- 1 | storage_path('app/input.php'), 5 | ]; 6 | -------------------------------------------------------------------------------- /images/demo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkboom/laravel-tinker-on-vscode/11dae2dfdde19a568f470d5f7140df6d3951695f/images/demo3.png -------------------------------------------------------------------------------- /images/demo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkboom/laravel-tinker-on-vscode/11dae2dfdde19a568f470d5f7140df6d3951695f/images/demo4.png -------------------------------------------------------------------------------- /images/demo5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkboom/laravel-tinker-on-vscode/11dae2dfdde19a568f470d5f7140df6d3951695f/images/demo5.png -------------------------------------------------------------------------------- /src/ExecuteCodeCommand.php: -------------------------------------------------------------------------------- 1 | option('use-dump')) { 27 | DB::enableQueryLog(); 28 | } 29 | 30 | $code = file_get_contents(Config::get('tinker-on-vscode.input')); 31 | 32 | if (!$this->option('use-dump')) { 33 | $code = preg_replace(['/^\s*dump\(/m', '/^\s*echo\s/m', '/^\s*dv\(/m'], '//', $code); 34 | } else { 35 | $this->line(''); 36 | $this->info('---------------------------------------'); 37 | 38 | $code = preg_replace(['/^\s*(dv\()([^)]+)(\))/m'], 'dump(\'${2}\', ${2}${3}', $code); 39 | } 40 | 41 | $code = $this->removeComments($code); 42 | 43 | $shell = $this->createShell(); 44 | 45 | $path = $this->getLaravel()->environment('testing') 46 | ? getcwd().'/vendor' 47 | : Env::get('COMPOSER_VENDOR_DIR', $this->getLaravel()->basePath().DIRECTORY_SEPARATOR.'vendor'); 48 | 49 | $path .= '/composer/autoload_classmap.php'; 50 | 51 | $config = $this->getLaravel()->make('config'); 52 | 53 | $loader = ClassAliasAutoloader::register( 54 | $shell, 55 | $path, 56 | $config->get('tinker.alias', []), 57 | $config->get('tinker.dont_alias', []) 58 | ); 59 | 60 | $output = $this->option('use-dump') ? $this->output : new BufferedOutput(); 61 | 62 | try { 63 | $shell->setOutput($output); 64 | 65 | $result = $shell->execute($code, true); 66 | } catch (\Throwable $exception) { 67 | $result = wordwrap($exception->getMessage(), 80); 68 | } finally { 69 | $loader->unregister(); 70 | } 71 | 72 | if ($this->option('use-dump')) { 73 | return; 74 | } 75 | 76 | $payload = [$result]; 77 | $payload[] = DB::getQueryLog(); 78 | 79 | Http::post(config('pick.pick-server'), [ 80 | 'data' => json_encode($payload), 81 | ]); 82 | 83 | DB::flushQueryLog(); 84 | DB::disableQueryLog(); 85 | } 86 | 87 | /** 88 | * @see: https://github.com/spatie/laravel-web-tinker/blob/master/src/Tinker.php 89 | */ 90 | protected function createShell() 91 | { 92 | $config = new Configuration([ 93 | 'updateCheck' => 'never', 94 | ]); 95 | 96 | $config->setHistoryFile(defined('PHP_WINDOWS_VERSION_BUILD') ? 'null' : '/dev/null'); 97 | 98 | $config->addCasters([ 99 | Collection::class => 'Laravel\Tinker\TinkerCaster::castCollection', 100 | Model::class => 'Laravel\Tinker\TinkerCaster::castModel', 101 | Application::class => 'Laravel\Tinker\TinkerCaster::castApplication', 102 | ]); 103 | 104 | return new Shell($config); 105 | } 106 | 107 | /** 108 | * @see: https://github.com/spatie/laravel-web-tinker/blob/master/src/Tinker.php 109 | */ 110 | public function removeComments(string $code) 111 | { 112 | $tokens = collect(token_get_all($code)); 113 | 114 | return $tokens->reduce(function ($carry, $token) { 115 | if (is_string($token)) { 116 | return $carry.$token; 117 | } 118 | 119 | $text = $this->ignoreCommentsAndPhpTags($token); 120 | 121 | return $carry.$text; 122 | }, ''); 123 | } 124 | 125 | /** 126 | * @see: https://github.com/spatie/laravel-web-tinker/blob/master/src/Tinker.php 127 | */ 128 | protected function ignoreCommentsAndPhpTags(array $token) 129 | { 130 | [$id, $text] = $token; 131 | 132 | if ($id === T_COMMENT) { 133 | return ''; 134 | } 135 | if ($id === T_DOC_COMMENT) { 136 | return ''; 137 | } 138 | if ($id === T_OPEN_TAG) { 139 | return ''; 140 | } 141 | if ($id === T_CLOSE_TAG) { 142 | return ''; 143 | } 144 | 145 | return $text; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/TinkerOnVscodeCommand.php: -------------------------------------------------------------------------------- 1 | prepareFiles(); 19 | 20 | $this->info("Write code in 'input.php' and save and see results at 'pick-server.test'"); 21 | 22 | $this->info("You can visit https://github.com/pkboom/pick-server to set up 'pick-server.test'."); 23 | 24 | $this->startWatching(); 25 | } 26 | 27 | public function prepareFiles() 28 | { 29 | file_put_contents(Config::get('tinker-on-vscode.input'), "name(Str::afterLast(Config::get('tinker-on-vscode.input'), '/')) 38 | ->files() 39 | ->in(Str::beforeLast(Config::get('tinker-on-vscode.input'), '/')); 40 | 41 | $watcher = FileWatcher::create($finder); 42 | 43 | Loop::addPeriodicTimer(0.5, function () use ($watcher) { 44 | $watcher->find()->whenChanged(function () { 45 | $code = file_get_contents(Config::get('tinker-on-vscode.input')); 46 | 47 | $viaTerminal = array_filter(['dump(', 'echo ', 'dv('], function ($expression) use ($code) { 48 | return strpos($code, $expression) !== false; 49 | }); 50 | 51 | if (count($viaTerminal)) { 52 | $this->call(ExecuteCodeCommand::class, [ 53 | '--use-dump' => true, 54 | ]); 55 | } 56 | 57 | exec('php artisan execute:code'); 58 | }); 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/TinkerOnVscodeServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 12 | $this->commands([ 13 | ExecuteCodeCommand::class, 14 | TinkerOnVscodeCommand::class, 15 | ]); 16 | } 17 | 18 | $this->publishes([ 19 | __DIR__.'/../config/tinker-on-vscode.php' => config_path('tinker-on-vscode.php'), 20 | ], 'config'); 21 | } 22 | 23 | public function register() 24 | { 25 | $this->mergeConfigFrom(__DIR__.'/../config/tinker-on-vscode.php', 'tinker-on-vscode'); 26 | } 27 | } 28 | --------------------------------------------------------------------------------