-
-
Notifications
You must be signed in to change notification settings - Fork 87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement plugin system and add PreserveWorkingTree plugin #125
Implement plugin system and add PreserveWorkingTree plugin #125
Conversation
Did I get that right that Im not sure it is the right thing to make any changes with a potentially empty configuration. |
It will always happen, but only if there are actions configured in |
Would it be possible to configure this functionality in an |
The only problem with it happening in an What do you think about a global config value that turns on/off this behavior? The main problem I ran into (which is why I decided to work on this) is because I had some hooks that changed file contents and then ran |
Building a switch to turn it on and off sounds actually perfect. |
Cool! I'll do that, then. |
8b75928
to
de92b2d
Compare
de92b2d
to
b53b84a
Compare
I decided to take a different route with this. Rather than create a new config option and hard-code this into the So, this PR now includes a plugin system for the Runner (though this could be expanded to support plugins that provide a list of pre-configured actions, etc.), and a |
This documentation supports the feature implemented in captainhookphp#125
This is now ready for review! |
@@ -73,10 +80,21 @@ class Config | |||
*/ | |||
public function __construct(string $path, bool $fileExists = false, array $settings = []) | |||
{ | |||
$pluginSettings = $settings['plugins'] ?? []; | |||
unset($settings['plugins']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my point it will be clean up PHP itself should be unscary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what you're saying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Lars is referring to the unset
and that PHPs internal garbage collector will clean it up automatically. But I'm not sure either :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I'm unsetting it here so that it doesn't cause any problems in getJsonArray()
here:
https://github.com/captainhookphp/captainhook/pull/125/files#diff-9727fe62ad60c6637a7def31b06f908a39f99f118a78280469d7a62eb67a9d68R260-R267
Maybe this is unnecessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, totally necessary since you are not redefining $config['plugins']
you are just appending.
$data['config']['plugins'][] = $plugin->toJsonData();
Maybe this is a good hint to change it up, remove the unset, but completely overwrite $config['plugins']
in toJsonData
to be sure.
Wow I like this approach much better then the previous one :) I really like the implementation. I'm still reviewing but I wanted to get your opinion on two points. 1. Plugin creationI'm not sure creating the plugin "inside" the 2. Plugin naming (just thinking out "loud")Currently the plugins are called |
I can't remember why I did it this way, but I agree that it shouldn't happen here. I'll move it back to inside
I have no preference for the I'll give it some more thought and post some ideas here for feedback before updating the PR. |
About the idea of a Something like this? /** @var \CaptainHook\App\Runner\Plugin **/
$plugin = $this->getPlugin($io, $config, $repository);
$class = '\\CaptainHook\\App\\Runner\\Hook\\' . Util::getHookCommand($this->hookName);
/** @var \CaptainHook\App\Runner\Hook $hook */
$hook = new $class($io, $config, $repository, $plugin); Then, in public function beforeAction(Runner\Hook $hook, Config\Action $action): void
{
foreach ($this->plugins as $plugin) {
$plugins->beforeAction($hook, $action);
}
} And this would get called in public function beforeAction(Config\Action $action): void
{
$this->plugin->beforeAction($this, $action);
} |
Currently the private function doConditionsApply(array $conditions): bool
{
$conditionRunner = new Condition($this->io, $this->repository, $this->hook); private function executeCliAction(Config\Action $action): void
{
$runner = new Action\Cli();
$runner->execute($this->config, $this->io, $this->repository, $action);
} I like the injection idea, but for now I think it is better to let the Other then the single sub-runner injection it looks great :) |
0459ffe
to
8f728dc
Compare
@sebastianfeldmann I've made changes based on your feedback. Let me know if these changes work. Thanks! |
Looks amazing, I have only one more question, but that's more an understanding thing then an issue preventing me from merging this thing. |
Did you forget to include your question, or am I missing it somewhere? 😄 |
I added it the the code, but had difficulties finding it as well 🙈 Here it is :) |
I did that on purpose because I thought it made sense for a plugin to have the ability to execute before and after the hook, even if there are no actions configured for the hook. Most of the time, a hook without any actions will probably be disabled, so it won't execute at all. I could go either way on this, though. What do you think? If a hook is enabled but has no actions configured, should the plugins be allowed to execute their |
If the hook is activated, but all actions are skipped because of pre conditions this might occure. In case of your plugin it would be fine to skip the patch creation, cleanup and patch re-applying but I understand the argument that we don't want to inhibit people from executing their plugin code anyway. |
src/Config/Plugin.php
Outdated
} | ||
|
||
$this->pluginClass = $pluginClass; | ||
$this->plugin = new $pluginClass(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure creating the plugin "inside" the Config\Plugin
is the right location. Actions
are not created by Config\Action
and Conditions
are not created by Config\Condition
. Currently the object creation is handled by everything under Runner\*
. So the Runner\Hook
should take care of it. It would be nice to separate it from the Runner\Hook
, maybe something like a Runner\Plugin
would be optimal for creating and executing the objects at least for consistency reasons. What do you think?
// if no actions are configured do nothing | ||
if (count($actions) === 0) { | ||
$this->io->write(['', '<info>No actions to execute</info>'], true, IO::VERBOSE); | ||
return; | ||
} else { | ||
$this->executeActions($actions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to move the $this->beforeHook();
and $this->afterHook();
inside the else statement.
So if no action will be performed the before and after is skipped as well. Or does it make sense to actually call before
and after
anyway?
Description
I went through several refactorings of this PR before I decided to open it up for final review.
In its current state, this diverges from the proposed functionality in #122 in that this implements a plugin system that allows for plugins that can execute code before and after a hook runs, as well as before and after each action runs. This allows extensions to hook into the
beforeHook()
,beforeAction()
, etc. methods to extend what CaptainHook can do.This PR also provides an initial plugin to illustrate the plugin behavior:
PreserveWorkingTree
. This plugin implements some of the feature I proposed in #122. When added to the plugins list incaptainhook.json
, this plugin will run before and after each hook to move any working tree changes out of the way and then reapply them. It will do this for any hook, since it's possible that an action in any hook could modify the working tree and/or staged files. Others can extend this class and implement theConstrained
interface to constrain it to only specific hooks.Notable Changes/Details
CaptainHook\App\Runner\Hook::beforeAction()
signature to include aCaptainHook\App\Config\Action $action
parameter. This is so we can pass this value to the plugin. Technically, this is a BC break, since this is a public method, and children ofHook
that override this method will need to update their signatures. However, in practice, I don't think CaptainHook provides a way to inject your own hook classes to override the ones inCaptainHook\App\Runner\Hook
, so this change might not affect any external users.CaptainHook\App\Runner\Hook::afterAction()
signature for the same reason.Hook::beforeAction()
andHook::afterAction()
into theHook::handleAction()
method, so they will now run for both PHP and CLI actions (previously, they only ran before and after PHP actions).Hook::beforeHook()
andHook::afterHook()
will always run, even if there are no actions. This allows runner plugins to run theirbeforeHook()
andafterHook()
methods, even if the hook has no actions configured. Previously, if there were no actions, these methods did not execute.Hook::shouldSkipActions(?bool $shouldSkip = null): bool
that allows plugins to tell the hook to skip any remaining actions.Hook::getName()
to return the string name of the hook, so plugins can use this.CaptainHookException
interface that all CaptainHook exceptions implement.Documentation
The following documentation has also been submitted as part of #130
Plugins
Actions and Conditions are suitable for most needs, but there may be times you need to add more functionality to CaptainHook, such as performing tasks before or after a hook runs. CaptainHook's plugin system provides this flexibility!
All CaptainHook plugins implement the
CaptainHook\App\Plugin\CaptainHook
interface. To use a plugin, add it to the plugins array in your config. If a plugin requires options, you may also provide those.Runner Plugins
Runner plugins are so-called because they execute during the hook runner stage of CaptainHook, allowing you to do things before and after the hook runs, as well as before and after each action in the hook.
To create a runner plugin, implement the
CaptainHook\App\Plugin\Runner
interface. Optionally, you may extend theCaptainHook\App\Plugin\Runner\Base
abstract class, which provides some basic functionality.To see an example runner plugin, check out
CaptainHook\App\Plugin\Runner\PreserveWorkingTree
.