MarkE Posted July 4, 2021 Posted July 4, 2021 I had been struggling to implement the 'noHooks' option in $page->save(). $page->save(['noHooks => true']) does not seem to work, nor does $pages->save($p, ['noHooks => true']) Then I found the following documentation in wire/core/Pages.php: * - `noHooks` (boolean): Prevent before/after save hooks (default=false), please also use $pages->___save() for call. So $pages->___save($p, ['noHooks => true']) does work. Just thought I'd mention it as it is a bit buried (and a bit unusual). Also, I'm not sure if it is possible to use the noHooks option with $page->save(). 2
Robin S Posted July 4, 2021 Posted July 4, 2021 10 hours ago, MarkE said: $page->save(['noHooks => true']) does not seem to work, nor does $pages->save($p, ['noHooks => true']) Not sure if it's just a forum typo but you should only have quotes around the key - it should be: $page->save(['noHooks' => true]); It seems to be working here for me. 10 hours ago, MarkE said: Then I found the following documentation in wire/core/Pages.php The PhpDoc comments form the basis of the online documentation: https://processwire.com/api/ref/pages/save/ So you're right that it wouldn't be hard to miss but it's not buried in the sense that you'd only find it by examining the core files. 10 hours ago, MarkE said: please also use $pages->___save() for call As a general thing, if you call any hookable method and include the underscores at the start of the method name then any hooks to that method won't be triggered. So I think the idea here is that the noHooks option takes care of making sure that several potential downstream hooks wont fire (e.g. Pages::saveReady, Pages::savePageOrFieldReady, Pages::saved, Pages::savedPageOrField, Pages::added) but in case Pages::save was itself hooked you'd need to call it with the underscores to prevent that hook from firing. But what's interesting is that the core itself doesn't call Pages::___save (with the underscores) when you do call Page::save() with the noHooks option true, and likewise with Pages::___saveField. Maybe the documentation for the noHooks option should be expanded to explain exactly which hooks are avoided, or the core should call Pages::___save and Pages::___saveField with underscores when noHooks is true. @ryan? 1
MarkE Posted July 4, 2021 Author Posted July 4, 2021 12 minutes ago, Robin S said: Not sure if it's just a forum typo It was. The code was correct. 13 minutes ago, Robin S said: It seems to be working here for me That's funny - I'll investigate further and post any info. I did a debug_backtrace in Tracy which clearly showed the hook being called despite 'noHooks' - maybe there was something else interfering.
MarkE Posted July 5, 2021 Author Posted July 5, 2021 Thanks for your help @Robin S I sort-of get it: $page->save(['noHooks' => true]); just prevents hooks running on Page::save - it does not prevent downstream hooks on Pages::save etc. My hook was on before Pages::save as advised elsewhere on this forum. See image below (ready() is needed to load the hook, debug_backtrace is running in the beforeSaveThis hook on Pages::save). However, if I try $pages->save($page, ['noHooks' => true]); then the hook on Pages::save still runs! EDIT: Ironically, this still only prevents hooks running on Page::save but $pages->___save($page, ['noHooks' => true]); works, as does plain $pages->___save($page); This all seems pretty confusing and inconsistent though (at least to my little brain). Perhaps @ryanor someone with greater knowledge than me could shed some more light on this (and clarify the documentation?). 1
Robin S Posted July 6, 2021 Posted July 6, 2021 15 hours ago, MarkE said: I sort-of get it: $page->save(['noHooks' => true]); just prevents hooks running on Page::save - it does not prevent downstream hooks on Pages::save etc. No, it does prevent a bunch of downstream hooks from firing, but specifically it doesn't include Pages::save and Pages::saveField (and I think it should include those methods). And to avoid confusion it would be good if the documentation explained exactly which hookable methods are affected by the noHooks option when it used in any of Page:save(), Pages::save() and Pages::saveField(). I've opened a GitHub issue so that Ryan is more likely to notice the problem and our discussion of it: https://github.com/processwire/processwire-issues/issues/1405 5 1
Jonathan Lahijani Posted August 5 Posted August 5 I really wish I knew about this issue 2 years ago. Like... really really wish.
Jonathan Lahijani Posted September 27 Posted September 27 Based on driving myself completely insane with noHooks for the last 2 years, and based on what Ryan specifically said here: Quote But in this case, this is an option that I don't think anyone should actually use, or at least it should be really rare. It was added for a specific and rare core purpose a long time ago, but I'm not sure that one even applies anymore. Maybe it should be removed or at least undocumented, as preventing hooks from running can be problematic, so we'd rather people didn't do it. For instance, Fieldtypes may use hooks to handle completion of a task at the right time, or to do cleanup. File fields are a good example. So preventing hooks could leave things undone in some cases. Is this an option that you need to use for something? If you can tell me more about it, maybe I should suggest other ways to do it. ... I completely agree with Ryan. noHooks option should be absolutely avoided. Seriously, if you use it in advanced cases like I have been doing, you will hit every WTF issue known to man. It is not made for developer use, even though it gives off that vibe. I think that is a mistake. I will write more about this in depth soon, but at least in my situation, my goal was to ultimately update a page and all of its descendant repeaters (repeaters within repeaters) only after the page is its descendant repeaters have been saved completely first without hook interference. Using noHooks basically fucks up everything up (saying it's been frustrating dealing with it is an understatement) and there are unintended consequences everywhere! The correct way to do what I described is to do something like this, which took forever to figure out (every line has a specific reasoning behind it): // // /site/classes/OrderPage.php // class OrderPage extends Page { public function getAggregateRootPage() { return $this; } public function finalize() { // your code to finalize the page, such as populating an order_total field, etc. } } // // /site/classes/OrderLineItemsRepeaterPage.php // class OrderLineItemsRepeaterPage extends RepeaterPage { public function getAggregateRootPage() { return $this->getForPage(); } } // // /site/init.php or /site/ready.php (doesn't matter) // wire()->set('finishedPages', new PageArray()); // hook 1: use Pages::saved only to build list of pages to finalize wire()->set('finishedPagesHook', wire()->addHookAfter('Pages::saved', function(HookEvent $event) { $page = $event->arguments('page'); if(!method_exists($page, 'getAggregateRootPage')) return; wire('finishedPages')->add($page->getAggregateRootPage()); // duplicated pages won't get stored }, [ 'priority' => 1000 ]); // hook 2: use ProcessWire::finished to finalize the pages 🤌 wire()->addHookBefore('ProcessWire::finished', function(HookEvent $event) { wire()->removeHook(wire('finishedPagesHook')); foreach(wire('finishedPages') as $finishedPage) { $finishedPage->finalize(); } }, [ 'priority' => 1001 ]); When I demo my system one day which is way more complicated than the example code above, it will become clear. // TLDR: don't use noHooks when saving a page. DON'T! Instead, create a log of what pages need to be finalized, and act on those pages in ProcessWire::finished hook, which is when you can be absolutely sure the coast is clear. I wish I knew about that hook earlier. If you follow those simple rules, you don't have to think about CLI vs non-cli, ajax vs. non-ajax, whether the current page process implements WirePageEditor, uncache, getFresh, saved vs. saveReady, before vs. after hook, hook priority, editing a repeater on a page vs. editing the repeater "directly", where the hook should go (it should be in init.php/ready.php or in the init()/ready() method of a module), etc. Note: there's a whole other aspect to this in terms of locking the a page to prevent multiple saves (like if a page was being saved by an automated script and the same page was being saved by an editor in the GUI). 3
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now