Jump to content

hooks for dummies


fruid
 Share

Recommended Posts

Though my profile says "senior" I still consider myself a newb when it comes to PW and an intermediate when it comes to php.

Nevertheless, I'm now trying to figure out hooks cause I have some actions to take place when something else occurs. So far I've been able to solve this with simple if-conditions. E.g. whenever a page with a specific template is created, it will always have a child page, the name of which will always be the same. So I just code if it doesn't exist with that name, create one, else do nothing. Not sure if that's the most elegant way but it does the job.

Now it gets trickier. I want to reset the value of a certain field of a page upon image upload. I went through the documentation for hooks but it's confusing, again, I'm not exactly a pro so I might need a more for-dummies-approach.

Not sure what's the difference between…

$wire->addHookAfter(…)
$this->addHookAfter(…) // is that inside my own function?
$page->addHookAfter(…)

and don't get me started on own hookable methods. Again, I'm still far from being an expert, but to my undestanding, 

you can hook a method of PW objects and add your own function to run before or after that method – makes sense to me.
But creating your own hooable method? Why would you want to make a method hookable if it's your own creation? Can't you just change the method without touching the core?

But that goes beyond my humble requirements anyway, just trying to understand the basics above ($wire, $this, …)

Here's what I need particularly:

$wire->addHookAfter('Inputfield(name=snipcart_item_image)::changed', function($event) { 
    $magazine->setAndSave('imagecolorat', '123456'); // just to test
});

So once I figured out how hooks work I might ditch the said if-conditions and use hooks instead.

Thanks for help!

Link to comment
Share on other sites

1 hour ago, fruid said:

Not sure what's the difference between…
 


$wire->addHookAfter(…)
$this->addHookAfter(…) // is that inside my own function?
$page->addHookAfter(…)

I know you mentioned docs being confusing, but I'd still suggest taking another look at them — specifically this part, as it explains this pretty clearly: https://processwire.com/docs/modules/hooks/#defining-hooks.

To simplify things a bit...

  • the key difference between the first two is that $wire->addHookAfter() works outside classes, while $this->addHookAfter() is how you'd usually attach the hook when you're in a method of a class (i.e. while you're working on a module of your own).
  • $page->addHookAfter() attaches the hook to the current Page object (which $page typically refers to), not all Page objects in general. Important distinction in case you're working with multiple pages and don't want your hook to run for all of them.

Not sure if that explains it any better than the docs, but again the biggest difference is just the context in which you're defining the hook (use $wire when in template files, /site/init.php, /site/ready.php, /site/admin.php, etc. and $this when you're within a method in a class) and whether you want your hook to apply to a single object ($page, $pages, or a specific item such as $mypage where $mypage = $pages->get('some_selector')) or all items of a specific type.

1 hour ago, fruid said:

But creating your own hooable method? Why would you want to make a method hookable if it's your own creation? Can't you just change the method without touching the core?

Most commonly you'll need your own hookable methods when you're, say, developing a reusable module. Instead of tweaking the module on a per-site basis, you'll likely want to keep it as-is and rather make some minor modifications by hooking into it's methods. This way you can easily update the module without having to redo or work around your modifications every single time.

On some edge cases you might have a utility function on the site, and then depending on some other event you may want to say that "at this specific instance the method should do something else", so you'd want to make it hookable. Can't think of many cases where that'd make sense, though.

1 hour ago, fruid said:

Here's what I need particularly:


$wire->addHookAfter('Inputfield(name=snipcart_item_image)::changed', function($event) { 
    $magazine->setAndSave('imagecolorat', '123456'); // just to test
});

So once I figured out how hooks work I might ditch the said if-conditions and use hooks instead.

Thanks for help!

I'm not sure what you're getting at here. Is this is just an example of what you're doing at the moment? Either way your hook looks fine to me.

Should've read this more carefully. You're accessing $magazine object here, but if this is your entire code, that won't work — you're not defining $magazine variable anywhere.

Since you're in function scope, you only have access to the arguments passed to that function, variables you've passed in with "use" language construct, and global functions. Even if $magazine exists outside your function, you'd have to pass it in with function($event) use ($magazine) { ... }

That being said, what you're probably looking for is something like Pages::savedPageOrField(). If you take a look at that docs page, there are code examples dealing with changes etc. Or, alternatively, Pages::savePageOrFieldReady(), which occurs right before the page/field is saved (which means that you can modify the values right before they are saved to the database).

... and please let me know if I missed your point here ?

Edited by teppo
  • Like 5
Link to comment
Share on other sites

yes, $magazine was a stupid typo. It needs to be $page, I think, because I'm on the page when the event happens – am I right?

Now I put:

$wire->addHookAfter('Inputfield(name=snipcart_item_image)::savedPageOrField', function($event) {
    $page->setAndSave('imagecolorat', '123456');
});


I put this code on the template of the page where the mentioned field 'snipcart_item_image' is. I then edit a page that uses that template in the CMS i.e. upload a new image to that field and save. The hook is supposed to set the value of the other field called 'imagecolorat' to '123456', but it doesn't.

Link to comment
Share on other sites

There are a few problems with that code:

  1. Switching $magazine to $page doesn't actually help at all. $page is not defined here either. As I mentioned in my previous post, you're in function context, and in that context you only have access to a) function arguments ($event), b) variables you've made accessible with "use" (currently there are none), and c) global functions. $page is none of those.
  2. Another issue is that there's no savedPageOrField method for Inputfield, so Inputfield(...)::savedPageOrField is never triggered — what you're looking for is Pages::savedPageOrField.
  3. From your post I assume that you've put this code in the template file, i.e. /site/templates/your_template_name.php? If so, the hook only applies to when you're viewing a page using that template, i.e. it has no effect in Admin. I'm not sure if that's what you really intended, but I'd assume not — more likely you'd want to put this hook somewhere it gets added in the admin as well, so perhaps /site/init.php or /site/ready.php.

... and, finally, even if your code did work, you would've likely ran into an infinite loop: if you hook into page save and save the page, that'll trigger the page save hook, which will then save the page and trigger the page save hook again, which... you know the drill. Pages::saveReady() is often better method to hook, as you can just modify the page values, and they'll get saved soon after (you don't have to call save in your hook) ?

In this case something along these lines could work:

// hook into Pages::saveReady
$wire->addHookafter('Pages::saveReady', function($event) {

    // get current Page object — in this case this is the first argument for the $event object
    $page = $event->arguments[0];

    // bail out early if current Page *doesn't* have the field we're interested in
    if (!$page->template->hasField('snipcart_item_image')) return;

    // ... and also bail out early if the snipcart_item_image field hasn't changed
    if (!$page->isChanged('snipcart_item_image')) return;

    // now that we know that snipcart_item_image has changed, set a custom value to another field
    $page->set('imagecolorat', 'YOUR_NEW_VALUE_HERE');

    // finally, populate our modified $page object back to the event
    $event->arguments(0, $page);

});

Note: written in browser and not properly tested.

  • Like 10
Link to comment
Share on other sites

  • 3 months later...

OK, picking up where I left off months later but still confused.

Still trying to figure out how to hook after the event of changing a field.

$wire->addHookAfter('Pages::savedPageOrField(snipcart_item_image)', function($event) use($item) {

doesn't work. I also tried with…

$wire->addHookAfter('Pages::saveField(snipcart_item_image)', function($event) use($item) {

which accords to what is described here: https://processwire.com/api/ref/pages/save-field/

but the hook is not triggered and It's not logic to me.


For a different hooking-scenario I tried 'Pages::trashed' which doesn't work but also 'Pages::trash' which in turn does work. Why, I know not. Both hooks are described in the API documentation in different places however. (below is the related thread)

Help is much appreciated!

 

 

Link to comment
Share on other sites

On 1/22/2021 at 7:33 PM, fruid said:

Still trying to figure out how to hook after the event of changing a field.



$wire->addHookAfter('Pages::savedPageOrField(snipcart_item_image)', function($event) use($item) {

doesn't work. I also tried with…



$wire->addHookAfter('Pages::saveField(snipcart_item_image)', function($event) use($item) {

which accords to what is described here: https://processwire.com/api/ref/pages/save-field/

but the hook is not triggered and It's not logic to me.

First off I would recommend re-reading this part of the hooks documentation: https://processwire.com/docs/modules/hooks/#conditional-hooks.

If we take a closer look at your second code example, here's what it's trying to do:

  1. Add hook after the saveField() method of the Pages class has been executed
  2. ... but only if the first argument to saveField() contains string value "snipcart_item_image"

Now, if you look at the definition of Pages::saveField() ...

	 * @param Page $page Page to save
	 * @param string|Field $field Field object or name (string)

... you can see that the first argument is actually a Page, not a string. In other words your condition doesn't really make sense here — what you're probably looking for is the second argument, which is the name of the field as a string or Field object. The conditional hooks documentation explains how to access a specific argument:

$wire->addHookAfter('Pages::saveField(1:snipcart_item_image)', function($event) use($item) {

Note: as you can see from the second @param definition, saveField() can receive either a string or an object. If there's a way to target both with conditional hook format, it's something I'm not aware of. I'm afraid that you're making things a bit complicated for yourself by trying to use the conditional hook syntax; it's an advanced feature that has it's use cases, but it's often easier to just hook into some method and "return" if the arguments are not what you are looking for.

But, alas — there's a potentially much bigger problem here: when you save a page, ProcessWire doesn't call saveField() for each field individually.

I don't know in what context you're trying to make your hook work so I can't say right away how you should write this hook, but I'd be interested to know if you gave my earlier example a try... and if so, did it do anything? If you're trying to change the value right before the page gets saved in the admin, you most likely should be hooking into Pages::saveReady. Or you could hook into Pages::savePageOrFieldReady if you also want to catch single field saves ?

Edit: keep in mind that even if you hook into Pages::savePageOrFieldReady, the field name (second argument for said method) is only populated if a single field was saved. The value is not going to be there if an entire page was saved. If you take a look at my previous reply and change it the code sample to use Pages::savePageOrFieldReady, it should actually be pretty close to catching both "full page saves" and single field saves.

 

  • Like 6
Link to comment
Share on other sites

  • 2 weeks later...

 

On 1/22/2021 at 7:49 PM, teppo said:

it's often easier to just hook into some method and "return" if the arguments are not what you are looking for.

I agree, this made things easier indeed.

I had some success and I think one issue was that the hooks were conflicting somehow, I'll elaborate further in the thread I linked above because that's the specific issue I needed help for, this thread here is rather for a general understanding.

On 1/22/2021 at 7:49 PM, teppo said:

	 * @param Page $page Page to save
	 * @param string|Field $field Field object or name (string)

 

Please don't tell me this is how you suggest me to know how each individual hook works ?  cause if it is then maybe hooks are not for me…

Thanks @teppo

 

Link to comment
Share on other sites

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...