This PR adds a new 'tinker.after-loop'
event, that gets fired after each execution
of a command in the tinker shell while.
Example
If this gets merged, you could add this snippet to the boot
method of your
AppServiceProvider
:
\Illuminate\Support\Facades\Event::listen('tinker.after-loop', function () {
echo 'You just executed something through tinker!' . PHP_EOL;
});
and the following output would be generated during a tinker session:
» php artisan tinker
Psy Shell v0.10.3 (PHP 7.3.11-0ubuntu0.19.10.3 — cli) by Justin Hileman
>>> echo 'Hi GitHub' . PHP_EOL
Hi GitHub
You just executed something through tinker!
>>> echo 'Another one!' . PHP_EOL
Another one!
You just executed something through tinker!
>>>
Motivation
This enables third-parties to react to commands executed during tinker sessions.
The concrete example that motivated this PR was Laravels telescope
package.
Executed queries, logs or model-events will not be recorded by telescope, as
the application will not shut down while inside the tinker-loop. As an
example that can be used for replication (executed in a fresh Laravel application with telescope
installed):
» php artisan tinker
Psy Shell v0.9.12 (PHP 7.3.11-0ubuntu0.19.10.3 — cli) by Justin Hileman
>>> App\User::create(['email' => '[email protected]', 'name' => 'I will not be recorded', 'password' => 'password']);
=> App\User {#3133
email: "[email protected]",
name: "I will not be recorded",
updated_at: "2020-04-10 11:49:55",
created_at: "2020-04-10 11:49:55",
id: 99,
}
After you close the shell and navigate to /telescope/models
, there is no entry
recorded, as the ListensForStorageOpportunities
-trait only handles the
termination of the app and worker loops
.
This caused confusion in the past
and could be fixed through the event implemented in this PR.
Implementation
Psy\Shell
has an array of $loopListeners
that listen for certain lifecycle events of the
shell.
These are implementations of the Psy\ExecutionLoop\Listener
-interface and can not be modified, once an
instance of Psy\Shell
was constructed. They are set initially to whatever getDefaultLoopListeners()
returns. For this reason, I created a new Laravel\Tinker\Shell
extends Psy\Shell
, overwrites the getDefaultLoopListeners()
-method and adds another Listener. This Laravel\Tinker\Shell\Listener\TinkerEventEmitter
-Listener then publishes the 'tinker.after-loop'
event in its afterLoop
-method, that gets called by Psy\Shell::afterLoop
.
The only modification to the existing Code is, that the TinkerCommand
now
needs to construct a TinkerShell
instead of the standard Psy\Shell
.
Notes
- I am not sure how I should add tests for this, as I did not found any existing tests that handle comparable situations. If tests are reqiured, it would be nice if someone could guide me in the right direction.
- I used the
event
-helper inside the TinkerEventEmitter::afterLoop
-method. If this is discouraged I will change it to use $this->app['events']->dispatch
or something like that.
If this gets merged, I would happily open another PR for telescope
that makes the ListensForStorageOpportunities
-trait also handle the 'tinker.after-loop'
-event and thereby make it work more reliably with tinker.