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

Custom hooks #2700

Open
jonathanbossenger opened this issue Jul 19, 2024 · 31 comments
Open

Custom hooks #2700

jonathanbossenger opened this issue Jul 19, 2024 · 31 comments
Assignees

Comments

@jonathanbossenger
Copy link
Collaborator

jonathanbossenger commented Jul 19, 2024

Details

  • Content type (Online Workshop, Lesson, Course, Tutorial, or Lesson Plan): Lesson
  • Content title: Custom hooks
  • Topic description: Learn to create custom hooks in your plugin in the same way that WordPress core creates hooks.
  • Audience (User, Developer, Designer, Contributor, etc.): Developer
  • Experience Level (Beginner, Intermediate, Advanced, Any): Intermediate

Prerequisites

It is assumed that the learner has already completed the following lessons:

Learning Objectives

  • Explain why it's a good idea to include custom hooks
  • Describe how to create a custom hook in your plugin
  • Provide an example of a custom filter and action hook.

Related Resources and Other Notes

Automation Code

//lesson

Copy link
Contributor

github-actions bot commented Jul 24, 2024

Lesson Development Checklist

  • Gather any relevant links to Support, Docs, or related material
  • Description and Objectives finalized
  • Lesson created and announced to the team for review
  • Lesson reviewed
  • Lesson video submitted and published to WPTV
  • Lesson created on Learn.WordPress.org
  • Lesson video published to YouTube
  • Lesson on Learn.WordPress.org updated with YouTube video
  • Lesson published to Learn.WordPress.org

@CrochetFeve0251
Copy link

@jonathanbossenger As you requested, I moved the comment to the right issue.

Should we talk about the fact filters are not returning a guaranteed type? (I didn't see it mentioned anywhere in the documentation)

I think that could help them adopt good practices from the standard and not spend hours trying to figure out what is the problem in their code when it comes from a wrong callback on the website from a client.

I also did some page about that topic here which explains the issue and how we are currently solving this at WP Media. Maybe it could inspire us for this tutorial.

@jonathanbossenger
Copy link
Collaborator Author

Thanks.

So this objective for this lesson will be focused on creating your own custom hooks in a plugin, either filter or action.

I agree that it's useful to know what data types are returned by filters. I've never experienced this but I'm guessing that this probably happens due to developers not returning the same type of data as the filter callback receives. To the best of my knowledge, WordPress core doesn't return different types when it hooks into filters.

It probably doesn't completely fit the objective for this lesson, though, unless we consider including a section on the fact that it's not really possible to "force" a specific data type to be returned from a filter callback.

However, what might work is if we added a new lesson to the Advanced Hooks module, which included what you've described here, and other things like Determining the Current Hook and Checking How Many Times a Hook Has Run.

These are smaller pieces of knowledge that are useful to the learner but don't quite have enough content to warrant an entire lesson on the subject.

What do you think?

@CrochetFeve0251
Copy link

CrochetFeve0251 commented Jul 31, 2024

Yes, the base issue comes from the callback returning the wrong value.

However, what we noticed at WP Media is that when that happens as the error message says it comes from the inside of the plugin, then clients tend to contact us to fix the issue. (We use extensively custom hooks in WP Rocket to keep the plugin standard while allowing users to modify its behavior to their needs)

I don't think the issue should be present in the core at there are reviews that prevent that kind of errors. However, it will be present for sure if a developer creates a plugin with custom hooks and uploads it on the WordPress repository as plugin users aren't always that experienced with WordPress code.

Then it could be part of a whole part by itself, or it can just also be a simple remark to make during the class when talking about the difference between actions and filters. All depends on the importance you give to that point.

PS:
My take on that issue is that filter value should be considered as values given by the user, thus it shouldn’t be trusted and sanitized before being used.

We do a bit of the same thing when working with data sent through forms by the users.

@jonathanbossenger
Copy link
Collaborator Author

jonathanbossenger commented Aug 3, 2024

Got it, I understand.

So, the goal for this lesson is to focus on developers creating their own custom hooks for others to extend, but this issue could also occur as you say, if developers even use core hooks incorrectly.

Therefore, I'm not sure a note about filter return values makes sense in this lesson, but I do think creating a new lesson described above does.

I've created a new issue here, and we can add this to that lesson.

@WordPress WordPress deleted a comment from github-actions bot Aug 11, 2024
@CrochetFeve0251
Copy link

CrochetFeve0251 commented Aug 15, 2024

I put my draft here, so it will be visible to everyone while I work on it.

Script

All websites have their own unique requirements.

As you might expect with your plugin, it is just impossible to be able to satisfy all of them at once with a closed code base they cannot modify cleanly.

That way your plugin will often end up being really close to fixing someone else's issue but always with one of two details that are missing to make it a perfect solution for them.

But what if I was telling you there was a way to not be the bad guy here and prevent users using your plugin from having to face that issue?

In that lesson we will be focusing on custom hooks: the perfect way to make your users able to make your plugin feat their needs perfectly.

Custom hooks

Custom hooks are just like regular hooks that you are using in WordPress and which are defined by the core.

However, this time you are behind the wheel.

Why use custom hooks?

Custom hooks are a great way to keep focus on your plugin main logic and avoid being slowed down by third-parties compatibilities as users have now a way to handle that kind of scenarios themselves and will report you their solution when they find one.

Another advantage of custom hooks is that it is possible for your user to customize your plugin behavior to their needs and link it to their own features.

Creating of a custom action

Custom actions are often created in one scenario: You want to warn something is going to happen or already happened.

For that it is possible to call the function do_action with the name of the action as a parameter.

do_action('my_action');

When necessary, it is then also possible to pass a context to the action the callbacks will be able to use adding more parameters to the function:

$count = 10;

$is_admin = true;

do_action('my_action', $count, $is_admin);

Once this is done, then context parameters will be available to be used by the callback functions on the action:

function my_callback(($count, $is_admin) {
 // custom logic
}

add_action('my_action', 'my_callback', 10 , 2);

Creation of a custom filter

Filters are a way to change a value that you rely on for your users.

As a custom hook, it is a great way to offer a choice for experts while making a decision for more novice users, preventing them from being confused with notions they don't master.

To create a custom filter, you need to use the function apply_filters with the name of the filter as the first parameter and the default value from the filter as the second parameter:

apply_filters('my_filter', false);

It is also possible to pass a context to callbacks adding more parameters:

$count = 10;

$is_admin = true;

apply_filters('my_filter', false, $count, $is_admin);

Once this is done, then context parameters are available this way on callbacks:

function my_callback(($value, $count, $is_admin) {
 // custom logic
}

add_filter('my_filter', 'my_callback', 10 , 3);

Conflicting name

WordPress has its own specificities when it comes to naming filters and actions.

If you don't want to end up with some weird filter or action behaviors, then you might be interested in using these rules.

Conflict between plugins

A WordPress plugin is like a flat.

By that I mean that you are not alone as there are other plugins and themes in the same WordPress instance.

This is why it is really important to understand that everyone shares the main namespace and thus you should not pollute it.

When it comes to custom hooks, you can respect that rule by always prefixing your hook names with the slug from your plugin:

do_action('my_plugin_my_action');

That way if someone else has created another with the same name, then it won't be conflicting with yours as each one will be inside their own namespace.

Conflicts between filters and actions

Behind the hoods, actions and filters are the same thing as for WordPress actions are just filters returning nothing.

This is why it is important to mind that naming a filter the same as an action will result in conflicts with callback registered to the action or filter being for the action and the filter.

For that reason you should always use unique names for each hook and not make the name being unique only to actions or filters.

@jonathanbossenger
Copy link
Collaborator Author

Let me know when you feel like you're finished with your first draft, and I'll review.

@CrochetFeve0251
Copy link

@jonathanbossenger while looking on the documentation there is a part about hook name conflicts, do you think that part should be here or in good practices? https://developer.wordpress.org/plugins/hooks/custom-hooks/#naming-conflicts

@jonathanbossenger
Copy link
Collaborator Author

@CrochetFeve0251 good question. I think adding it to this lesson makes a lot of sense.

@CrochetFeve0251
Copy link

@jonathanbossenger I think I am good for that script feel free to review it.

@jonathanbossenger
Copy link
Collaborator Author

@CrochetFeve0251

Nice work. I have two general points of feedback from my first review.

  • The opening paragraph: It's very possible that someone watching this lesson has just completed the beginner developer course, and so they've never experienced this. It might be useful to introduce this problem as if it's the first time someone has encountered it. That way those who have experienced it will simply read and agree, but someone who hasn't will also be able to understand the problem.
  • In your code examples, I see you are using closures (aka anonymous functions) for the callbacks. While this is not wrong, for Learn WordPress content we prefer to stick to using WordPress Coding Standards, which currently do not recommend using closures for action and filter callbacks, until this ticket is resolved at least.

There are some other grammar-related items, but I think a good first step is to update the script based on these changes, and then I can do a grammar review.

@jonathanbossenger
Copy link
Collaborator Author

Hi @CrochetFeve0251 I just wanted to check if the script is ready for a second review?

@jonathanbossenger
Copy link
Collaborator Author

@webcreativeng we are getting close to having a script ready to create a video from, I wanted to check if you are still available to create videos for this module?

@CrochetFeve0251
Copy link

Hi @CrochetFeve0251 I just wanted to check if the script is ready for a second review?

@jonathanbossenger Sorry I forgot about the intro.

What do you think about the new intro?

@jonathanbossenger
Copy link
Collaborator Author

Edited script

Introduction

As a plugin developer, you’ll probably try to cover all possible use cases when building your plugin’s functionality.

However, all websites have unique requirements, and it’s impossible to satisfy all of them.

This sometimes means your plugin will end up being really close to what a potential user needs but might need one or two minor additions that would make it a perfect solution.

Fortunately, there was a way to allow your plugin’s users to extend its functionality without editing the plugin code.

In that lesson, you will learn about custom hooks, the perfect way to enable your users to customize your plugin to their needs.

Custom hooks

Custom hooks are just like regular hooks you’ve already learned about, which are defined by WordPress Core.

However, this time, you are behind the wheel. You can create either custom action hooks or custom filter hooks through your plugin code, to enable other users or developers the ability to extend the functionality of your plugin.

Why use custom hooks?

Custom hooks are a great way to keep focus on your plugin’s main functionality.

They make it possible for your plugin user to customize your plugin behavior to meet their needs and implement their own features.

They also allow you to avoid having to worry about making your plugin compatible every single possible external integration.

As custom hooks give users a way to implement their own custom scenarios themselves they will often report their solution back to you when they find one, which you can share with the rest of your users.

Creating a custom action

You would often create custom actions to trigger before something specific is going to happen or after something specific has happened.

To do this, you call the do_action function, passing it the name of the action as a parameter.

do_action('my_action');

It is also possible to pass a context to the action.

do_action('my_action', ‘context’);

The context parameters will then be available to be used by any callback functions hooked on the action:

$count = 10;

$is_admin = true;

do_action( 'my_action', $count, $is_admin );
add_action( 'my_action', 'my_callback', 10 , 2 );
function my_callback( $count, $is_admin ) {
    // custom logic
}

Creating a custom filter

Custom filters are a way to allow someone to change the value of something you define in your code.

It allows you to make a specific decision for how your code functions for plugin users, but also allow more experienced users the ability to extend that decision to suit their requirements.

To create a custom filter, you call the apply_filters function with the name of the filter as the first parameter and the default value the filter is applied to as the second parameter:

$enabled = false
apply_filters('my_filter', $enabled);

With this in place, someone could hook into the filter, and set the value to true, if this was their requirement.

Like custom actions, it is also possible to pass a context parameter:

$count = 10;

$is_admin = true;

apply_filters('my_filter', false, $count, $is_admin);

Once this is done, then context parameters are available on callback functions hooked into the filter:

add_filter( 'my_filter', 'my_callback', 10 , 3 );
function my_callback( $value, $count, $is_admin ) {
    // custom logic
}

Naming conflicts

In the lesson on Naming Collisions, you learned how to avoid naming conflicts in the global namespace. This is also true when creating custom hooks.

Conflict between plugins

When creating custom hooks, you should always prefix your hook names with a unique identifier, ideally the same one used elsewhere in your plugin:

do_action('my_plugin_my_action');

That way, if someone else creates another hook with a similar name, it won't conflict with yours, as each one will be prefixed with its own unique identifier.

Conflicts between filters and actions

Under the hood, actions and filters are functionally the same, the main difference being that actions don’t return a value and filters do.

For that reason, you should always use unique names for each hook. Don’t create an action and a filter with the same name.

// This is wrong
do_action('my_hook');
apply_filters('my_hook', $some_variable)

Doing this will result in conflicts with callback functions registered to the action or filter, as it means the callback will run both when the action and filter are triggered.

Depending on whether you hook a callback into the action or filter, it may also cause errors in the code execution.

@jonathanbossenger
Copy link
Collaborator Author

@CrochetFeve0251, thanks for the updated intro.

I have edited your script, mostly to improve its clarity, tweak some of the code examples, and refer to the Naming collisions lesson, which was recently added to the first module.

Let me know if you're happy with the edited version or if you have any further edits/suggestions.

Once you're happy with it, we can pass it onto @webcreativeng to start creating the video, and get started on the next lesson script.

@CrochetFeve0251
Copy link

@jonathanbossenger it seems good to me

@jonathanbossenger
Copy link
Collaborator Author

jonathanbossenger commented Aug 30, 2024

@webcreativeng this video is ready to be created. Are you able to give us an estimated time for a first draft?

@github-project-automation github-project-automation bot moved this to 👋 Ready to Create in LearnWP Content - Development Aug 30, 2024
@jonathanbossenger jonathanbossenger moved this from 👋 Ready to Create to 🚧 Drafts in Progress in LearnWP Content - Development Aug 30, 2024
@webcreativeng
Copy link

Edited script

Introduction

As a plugin developer, you’ll probably try to cover all possible use cases when building your plugin’s functionality.

However, all websites have unique requirements, and it’s impossible to satisfy all of them.

This sometimes means your plugin will end up being really close to what a potential user needs but might need one or two minor additions that would make it a perfect solution.

Fortunately, there was a way to allow your plugin’s users to extend its functionality without editing the plugin code.

In that lesson, you will learn about custom hooks, the perfect way to enable your users to customize your plugin to their needs.

Custom hooks

Custom hooks are just like regular hooks you’ve already learned about, which are defined by WordPress Core.

However, this time, you are behind the wheel. You can create either custom action hooks or custom filter hooks through your plugin code, to enable other users or developers the ability to extend the functionality of your plugin.

Why use custom hooks?

Custom hooks are a great way to keep focus on your plugin’s main functionality.

They make it possible for your plugin user to customize your plugin behavior to meet their needs and implement their own features.

They also allow you to avoid having to worry about making your plugin compatible every single possible external integration.

As custom hooks give users a way to implement their own custom scenarios themselves they will often report their solution back to you when they find one, which you can share with the rest of your users.

Creating a custom action

You would often create custom actions to trigger before something specific is going to happen or after something specific has happened.

To do this, you call the do_action function, passing it the name of the action as a parameter.

do_action('my_action');

It is also possible to pass a context to the action.

do_action('my_action', ‘context’);

The context parameters will then be available to be used by any callback functions hooked on the action:

$count = 10;

$is_admin = true;

do_action( 'my_action', $count, $is_admin );
add_action( 'my_action', 'my_callback', 10 , 2 );
function my_callback( $count, $is_admin ) {
    // custom logic
}

Creating a custom filter

Custom filters are a way to allow someone to change the value of something you define in your code.

It allows you to make a specific decision for how your code functions for plugin users, but also allow more experienced users the ability to extend that decision to suit their requirements.

To create a custom filter, you call the apply_filters function with the name of the filter as the first parameter and the default value the filter is applied to as the second parameter:

$enabled = false
apply_filters('my_filter', $enabled);

With this in place, someone could hook into the filter, and set the value to true, if this was their requirement.

Like custom actions, it is also possible to pass a context parameter:

$count = 10;

$is_admin = true;

apply_filters('my_filter', false, $count, $is_admin);

Once this is done, then context parameters are available on callback functions hooked into the filter:

add_filter( 'my_filter', 'my_callback', 10 , 3 );
function my_callback( $value, $count, $is_admin ) {
    // custom logic
}

Naming conflicts

In the lesson on Naming Collisions, you learned how to avoid naming conflicts in the global namespace. This is also true when creating custom hooks.

Conflict between plugins

When creating custom hooks, you should always prefix your hook names with a unique identifier, ideally the same one used elsewhere in your plugin:

do_action('my_plugin_my_action');

That way, if someone else creates another hook with a similar name, it won't conflict with yours, as each one will be prefixed with its own unique identifier.

Conflicts between filters and actions

Under the hood, actions and filters are functionally the same, the main difference being that actions don’t return a value and filters do.

For that reason, you should always use unique names for each hook. Don’t create an action and a filter with the same name.

// This is wrong
do_action('my_hook');
apply_filters('my_hook', $some_variable)

Doing this will result in conflicts with callback functions registered to the action or filter, as it means the callback will run both when the action and filter are triggered.

Depending on whether you hook a callback into the action or filter, it may also cause errors in the code execution.

Great. I guess I can get started. I've looked at some existing videos for context for my video, but I'm just wondering if there is documentation on 'video creating' for proper guidance.

As per the timeline, I guess I can work to have a draft before WCUS, if that's okay.

Thank you

@jonathanbossenger
Copy link
Collaborator Author

@webcreativeng thanks.

I've looked at some existing videos for context for my video, but I'm just wondering if there is documentation on 'video creating' for proper guidance.

We do have some guidelines, but they are not very prescriptive.

There are some tips on creating the video portion of a lesson in the Creating a Lesson page

There is also the Video best practices, which contains a lot of useful information. For these types of technical videos, I recommend reading the section on Guidelines for creating technical videos that include code examples.

One thing you could try is to maybe just focus on a smaller section of the script as a first draft, and then share it here for feedback. That way you get a feel for the process, and you can get feedback on the video output.

@jonathanbossenger
Copy link
Collaborator Author

@webcreativeng I am just doing some check-ins, and I wanted to make sure you still feel comfortable getting your first draft ready before WCUS, which is next Tuesday, 17 September.

Let me know if there's anything I can do to support you.

Thanks

@webcreativeng
Copy link

Thanks @jonathanbossenger
I'm working on the first draft and will post it soon. While going through the script, I have made a few slight adjustments either omissions or just making the conversation more relatable, without losing the script. I hope that is fine.

Regards,

@jonathanbossenger
Copy link
Collaborator Author

I'm working on the first draft and will post it soon. While going through the script, I have made a few slight adjustments either omissions or just making the conversation more relatable, without losing the script. I hope that is fine.

Yup, that's completely fine. If possible, could I ask you to either document those changes here or (even better) submit them as a pull request on the original script in this repository, as the script will be used for the lesson content when the lesson is added to Learn.WordPress.org.

@webcreativeng
Copy link

Ok, here is the first part of the video, draft, for feedback before I complete the rest.

https://youtu.be/XHnb9EIxnDQ

Thank you.

@jonathanbossenger
Copy link
Collaborator Author

@webcreativeng thank you, I will review it this week.

@jonathanbossenger
Copy link
Collaborator Author

jonathanbossenger commented Oct 1, 2024

It looks great, @webcreativeng; I especially like the graphic you used for the "close but not quite" scenario.

No immediate feedback at this time, please feel free to go ahead and complete the rest of the video.

EDIT: it would be helpful if you're able to share a found idea of what you'll have the final video ready for review.

@jonathanbossenger
Copy link
Collaborator Author

@webcreativeng I'm checking in to see how this video progresses. I would also like to mention that if things have gotten busy for you and you can't contribute now, that's also OK. Just let us know. Thanks

@jonathanbossenger
Copy link
Collaborator Author

jonathanbossenger commented Nov 7, 2024

Video for review: https://drive.google.com/file/d/1ITUiBfd3stRbUZuFIn9eEjsKOXvth44f/view?usp=drive_link

Note to reviewers, at about 4:41 there is a screenhost of a GitHub issue for a lesson. This lesson hasn't been published yet, as soon as it is I will add it to the video.

@jonathanbossenger jonathanbossenger moved this from 🚧 Drafts in Progress to 🔎 Ready for Review in LearnWP Content - Development Nov 7, 2024
@jonathanbossenger jonathanbossenger moved this from 🔎 Ready for Review to ✅ Preparing to Publish in LearnWP Content - Development Dec 9, 2024
@jonathanbossenger
Copy link
Collaborator Author

WordPress.tv: https://wordpress.tv/2024/12/23/custom-hooks/

@jonathanbossenger
Copy link
Collaborator Author

jonathanbossenger commented Dec 23, 2024

Chapters:

00:00 Introduction
00:25 Why custom hooks?
01:57 Creating custom action hooks
02:53 Implementing custom filter hooks
03:33 Handling arguments
04:42 Avoiding naming collisions
05:13 Best practices for custom hooks
06:01 Further reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: ✅ Preparing to Publish
Development

No branches or pull requests

3 participants