-
-
Save everzet/1406938 to your computer and use it in GitHub Desktop.
<?php | |
$watcher = new Symfony\Component\ResourceWatcher\ResourceWatcher; | |
// track any change inside directory: | |
$watcher->track('some/folder1', function($event) { | |
echo '['.$event->getType().'] '.$event->getResource()."\n" | |
}); | |
// track only creations inside directory: | |
$watcher->track('some/folder2', function($event) { | |
echo $event->getResource()." was created\n" | |
}, Symfony\Component\ResourceWatcher\Event\Event::CREATED); | |
// track only *.xml file changes inside directory: | |
$watcher->track( | |
new Symfony\Component\Config\Resource\DirectoryResource('some/folder3', '/\.xml$/'), | |
function($event) use($watcher) { | |
echo '['.$event->getType().'] '.$event->getResource()."\n" | |
// stop tracking when first even occurs: | |
$watcher->stop(); | |
} | |
); | |
// start tracking: | |
$watcher->start(); |
@schmittjoh it's little bit different - you specify the file filters and event types you wanna track before starting to watch on them and then you just receive events to mapped callable. Nobody will implement interface with 8 methods, when they only need 1 :-)
Yeah, you can provide an empty implementation for all of them. But callables are not so easily testable like a proper interface, you should care about that :)
Thinking about it, wouldn't it also be nice to give the watcher a set of directories/files, and then have the listeners triggered for all of them?
👎 for the interface, testing is a false argument to over-complicate things. What you want to test here is if the proper event is triggered if you touch() or unlink(). No need for an interface.
👍 for the possibility to give an array of paths, or even a glob expression.
Using an object allows to re-use code, properly inject dependencies, etc. testing is one little part that I picked. As a side-effect, we would allow people to use regular services as watchers, and it would even be possible to only have one watch command contrary to separate watch commands for each library that needs one (which isn't very efficient).
Don't get me wrong, I think the closures are very nice for simple things where you want to get something going fast. everzet surely did a good job there. On a framework-scale however, where observers might come from different components/bundles it feels not good enough. Maybe it would make sense to re-use the event dispatcher code?
@schmittjoh i'm not sure that i have vision of yours. Could you make a proof of concept based on my PR?
Here is a quick sketch how it might look like. The main difference lies in re-using the existing event dispatcher code.
<?php
class Watcher
{
private $resources = array();
private $dispatcher;
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
public function track($directory, $filePattern = null, $caseSensitive = true)
{
$this->resources[] = array($directory, $filePattern, $caseSensitive);
}
public function start()
{
// dispatch events through the EventDispatcher when they occur
}
}
$dispatcher = new EventDispatcher();
$dispatcher->addListener('watcher.file_changed', array(new MyService(), 'onFileChanged'));
$dispatcher->addListener('watcher.file_changed', array('my.service_id', 'onFileChanged'));
$dispatcher->addListener('watcher.file_changed', function(FileChangeEvent $event) {
unlink($event->getFile());
$event->stopPropagation();
});
$dispatcher->addListener('watcher.file_changed', new FilteredListener('some/dir', function(FileChangeEvent $event) {
// only called for files inside some/dir
}));
$watcher = new ResourceWatcher($dispatcher);
$watcher->track('some/dir');
$watcher->track('some/other/dir', '*.xml')
$watcher->watch();
I kinda like the idea of reusing EventDispatcher
, but i don't like listeners post-filtration and duplication of paths in order watch for a specific path.
Filtration (regex) and tracking parameters should be specified before calling start()
in order for component to be efficient and fast. And we shouldn't require users to specify same paths or regexps multiple times in order for component to be usable.
I agree with you on both points. If we concur that using the EventDispatcher is a good idea, we could start to add some sugar to the raw version above.
a) When used inside Symfony2, we will probably have one watch command (php app/console watcher:start
), and all interested components can hook up with this command via tagging respective services.
services:
my_listener:
tags:
- { name: event_listener, event: watcher.file_changed }
- { name: watcher.resource, dir: some/dir, pattern: *.xml }
- { name: watcher.resource, dir: another/dir }
This would then be desugarized to the extended version above.
b) When used standalone, we could add a convenience method addListener
to the Watcher class which could look like this:
<?php
class Watcher
{
// ...
public function addListener($resource, $event, $callable, $priority)
{
$this->track($resource);
$this->dispatcher->addListener($event, new FilteredListener($resource, $callable), $priority);
}
}
This way we get the best of both worlds. Simple and easy when used standalone and also fits nicely in the Symfony2 Framework context. What do you think?
What about adding tracking_id to event names? For example:
<?php
$watcher = new Watcher();
// We can require users to specify unique tracking id in `track()` method:
$watcher->track('twig_templates', new DirectoryResource('/some/dir', '/regex/'));
And this way, watcher will send both watcher.twig_templates.file_changed
and
watcher.file_changed
events for this particular tracking. In this case, user will be able to
choose whether he needs to receieve all or only specific-track events. This will remove
the need in FilteredListener
, which complicates things a lot.
I guess by now everyone are agreeing on accepting both a callable and an object so I would not jump in on that.
I'm more inclined towards @everzet last option. Way easier to understand and use.
Yes, sounds good to me as well.
How about reversing the order of arguments function track($resource, $alias = null);
?
Well, there should be 3 parameters at least - $resource
, $trackingId
and $trackedEvents
because there's no reason to track any folder change if user only interested in a file_change
How about adding an interface in addition to callables?