Skip to content
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

Add Event implementation #150

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Add Event implementation #150

wants to merge 1 commit into from

Conversation

bobmulder
Copy link
Contributor

Hi all,

I know we have had this discussion before, but today I've created a small and simple implementation of events in Couscous.

I want to open this PR to share the idea for now. When you agree with this feature I will add tests and documentation.

For using events I have used the library of ThePHPLeauge: http://event.thephpleague.com/2.0/

The following events are supported:
- couscous.step.after_config
- couscous.step.before_parse
- couscous.step.after_parse
- couscous.step.before_render_markdown
- couscous.step.after_render_markdown
- couscous.step.before_write
- couscous.step.after_write

To show you where the event exactly has been fired, I have implemented one listener per event with a var_dump() with the event-name in it.
In every event, the $project variable is accessible. However because a bug in the Event-library (which is fixed in thephpleague/event#66) it's a bit dirty now, but soon it will be better ;)
To show you that it becomes very easy to customize the markdown process I have replaced the word couscous to CakePlugins.

This feature would be very helpful to extend Couscous locally. This is very helpfull when you want to work with small features who can't be realized quickely in the Couscous library.
In future we could extend this feature by using external events from external resources loaded via Composer.

I would like to know how you think about this!

@wysow
Copy link
Member

wysow commented Feb 17, 2016

For me it seems like a really good idea to be able to customize some parts of Couscous internal mechanism.

Just maybe we can find a way to avoid duplicate code in *Event classes like https://github.com/CouscousPHP/Couscous/pull/150/files#diff-4990fde51d0c5639e97a76c2feca4c51R16 no?

@bobmulder
Copy link
Contributor Author

Thank you for the reply! Avoiding duplicates would only be able via the following options:

1. Single step with variables

Example code for the step:

class EmitEvent implements Step
{
    public function __invoke(Project $project, $parms = [])
    {
        $emitter = EventManager::emitter();
        $emitter->emit($params['event'], ['project' => $project]);
    }
}

And in the config.php:

    DI\get('Couscous\Module\Events\Step\EmitEvent', ['event' => 'couscous.step.after_config']),

Warning: Pseudo code here ;)

2. Emits inside the existing steps

For example, the couscous.step.after_config event could be placed inside the ExecuteBeforeScripts- or OverrideBaseUrlForPreview step.
Example:

class OverrideBaseUrlForPreview implements \Couscous\Step
{
    public function __invoke(Project $project)
    {
        if ($project->metadata['preview'] === true) {
            $project->metadata['baseUrl'] = '';
        }

        $this->emitEvent($project);
    }

    public function emitEvent($project) {
        $emitter = EventManager::emitter();
        $emitter->emit('couscous.step.after_config', ['project' => $project]);
    }
}

I would like to hear what you prefer. The first option seems not to be possible I guess, but the second option looks very dirty...

Also I would like to know from @mnapoli if I covered all steps of the proces, so we can inject everywhere in the proces we want.

@bobmulder
Copy link
Contributor Author

What about the events? I need some custom stuff soon, so I would like to talk about this...

@bobmulder
Copy link
Contributor Author

Any feedback on this? I would like to do some stuff with it :)

@wysow
Copy link
Member

wysow commented Feb 25, 2016

@mnapoli what do you think of this?

@bobmulder
Copy link
Contributor Author

So I guess there is no need for contributors here?

@mnapoli
Copy link
Member

mnapoli commented Mar 2, 2016

Hi! Sorry for letting this fall behind (so much stuff, so little time). I was drafting a response over a few days, but still not finished :( Let's try again in a shorter way.

I'm not 100% convinced by this approach. Maybe it's because of details, but maybe it's because of the architecture itself. Let's start by details:

  • events expose internal details, like before_render_markdown: what if we support Asciidoc or Rst (RestructuredText support #64)?
  • the events means the internal workflow of Couscous becomes "public API" (i.e. we need to keep BC). E.g. what if we change the order of something, add new steps to the workflow, etc.
  • the way it's implemented in the PR means we have a double architecture: a file of steps + events triggered at arbitrary points

I've been trying for the last 2 weeks to suggest a better approach, I haven't finished due to lack of time but I think I've identified a few things:

  • Markdown processing should become a single step, and there should be ways to add new Markdown processing steps independently of the whole Couscous workflow.

    I've tried to use a single concept (Step) for different things (processing a Markdown file is different from processing the whole project), and it's showing its limits. If we make the Markdown processing a single Step, with internal extension points, it will be cleaner, simpler, and it will be possible to let end users register custom Markdown processors independently of letting users extending the whole Couscous workflow.

  • Same as with Markdown, I think we should try to reduce the number of steps to a minimum, wrap bigger logic in macro steps and let each steps have its extension points. That will make the workflow simpler, and reduce the need for extension points in the whole workflow (e.g. less events if we keep the event architecture)

  • Maybe we should move out of the config loading outside of the steps: it comes before the whole workflow. It will make the workflow simpler since it removes a major step

I had other ideas but don't have them in mind right now, I wanted to let you know I definitely haven't forgotten about this, I'm just trying to find the better approach and I don't have a clear solution yet.

@bobmulder
Copy link
Contributor Author

Hi Matthieu,

Thank you for your well thought idea about this. I will give you some more time to work this out. I would like to hear from you!

@bobmulder
Copy link
Contributor Author

Is there any update on this? I would like to see CousCous extendable ;)

@geekish
Copy link

geekish commented Apr 15, 2016

+1 for this, or something similar. I'm experimenting with extending Couscous to generate a simple blog, and this would make it much easier.

@bobmulder
Copy link
Contributor Author

Thanks @geekish. I would love to experiment with extensions as well. I would like to hear of @mnapoli what has to be done to merge this PR. Probably @geekish can help with the final changes?

@geekish
Copy link

geekish commented Apr 18, 2016

I don't know how much help I could be but I would like to try. :)

In my experiment, I'm using the ContainerFactory to create the container and retrieve the steps from there to add my own.

If the current process were reduced to a single step per Module, I could take advantage of an event listener that fires during certain parts of step execution, but I can see how this could get complicated.

Maybe a better approach would be for each step (or each individual part of a step if they are combined) to implement an interface, say, so upon execution each Step actually calls a function defined by the interface so I could swap it out all together with my own class implementing said interface.

I hope this makes sense without any concrete examples.

@bobmulder
Copy link
Contributor Author

@geekish Can you describe how we could add those events/interfaces when extending Couscous? Do you have any code of your suggestion online?

@mnapoli
Copy link
Member

mnapoli commented Apr 18, 2016

@bobmulder & @geekish what is your use case? I.e. what do you want to do with such extension? I think it could help to have concrete example of what to do with extensions, in order to better define the scope of that feature.

I'm still not convinced by the event approach that we are discussing here. It's exposing too much internal stuff (meaning also we can't change it later). Also events have a tendency of breaking all the flow of the application into a big spaghetti plate (for example try to debug a hook-driven architecture step by step…). All the other points of my previous comment still stand. I'm afraid we will need to add more and more events as Couscous evolves, and that current events might limit us in the future.

I'd rather try to improve the internals of Couscous step by step in order to decouple parts first and leave more room for macro modules. E.g. extract all the Markdown processing in its own independent module (e.g. one single step as you said @geekish). That way the one step that processes Markdown can be replaced by something that processes (for example) RsT/Asciidocs.

@geekish
Copy link

geekish commented Apr 20, 2016

I will try today to write up a concrete example of my ideas. :)

@bobmulder bobmulder mentioned this pull request Sep 9, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants