Intro
Sometimes, people don't think about what they're doing. I've seen crap like 5000 line files looking like copy and paste from StackOverflow not once. If it works, then it works, right? 🤷♂️ I agree with this, but the question is how does it work? I remember such cases really well because it was me who wrote this. I learned then that even the most basic decisions made without careful consideration can make the product suffer.
So today I'll talk about the most basic decision that needs to be made before starting any project: "Procedural or OOP? Which should be chosen?" I'll discuss the advantages and trade-offs of each, and provide basic concepts that should help you to make careful decisions on the winning strategy for your WordPress projects. I'll try to answer:
- How to write procedural code in WordPress?
- How to use OOP in WordPress?
- What are the differences between both?
- Which of them should be used? - Based on the story of Mark's company transition.
The Goal
Let's implement the first business needs in my application. Displaying links to five recently added teams after post content. I can split that to two simple requirements:
- Register
Teams
Custom Post Type. - Display links to five recently added teams.
How to write procedural code in WordPress?
Functionality is organized in a linear manner, often implemented in simple files like themes' functions.php
. Involves defining functions or classes in the global scope, making them accessible throughout the app. Focuses on the simplicity and fast implementations, reducing the meaning of clear boundaries and separated responsibilities.
Example
To meet the goal I just need to create two functions in the theme's functions.php
file. I prefix them to avoid naming collisions with other themes, plugins or the core.
function fm_register_teams_cpt(): void
{
register_post_type('team', [
'labels' => [
'name' => __('Teams', 'fm'),
'singular_name' => __('Team', 'fm'),
],
'public' => true,
]);
}
add_action('init', 'fm_register_teams_cpt');
function fm_add_teams_to_posts_content(string $content): string
{
$teams = get_posts([
'post_type' => 'team',
'posts_per_page' => 5,
]);
if (! empty($teams)) {
$links = [];
foreach ($teams as $team) {
$links[] = sprintf('<a hre="%s">%s</a>', get_permalink($team), get_the_title($team));
}
$content .= sprintf(__('Latest Teams: %s', 'fm'), join(', ', $links));
}
return $content;
}
add_filter('the_content', 'fm_add_teams_to_posts_content');
If you choose this approach please do not put everything in single file! It makes the code organization, readability and reusability hell. Try to use Seaparation of Concerns to split the code to several files.
require_once(FM_PATH . '/app/posts.php');
require_once(FM_PATH . '/app/teams.php');
Keypoints
- Recommended choice for beginners as it does not demand a high level of knowledge. Makes understanding how does the WordPress work easier.
- Effective in solving straightforward problems or when fast implementation needed.
- A common pitfall is tendency to write code without clear boundaries, leading to long and too complex files what makes the maintaining and modifying the codebase over time challenging.
- Collaboration might be difficult due to potential problems of working together on the same file even with GIT merging tools.
How to use OOP in WordPress?
Approach organizes functionality into modular and reusable objects or classes. These objects encapsulate data and behavior, promoting a more structured and organized codebase. OOP focuses on creating well-defined boundaries and separating responsibilities, allowing for easier maintenance, scalability, and code reusability.
Modules
At first, I need to use separation of concerns and define modules that will be used then to build the whole application. Teams
which will be responsible for managing team
type behaviour like creating CPT, and similar Posts
module which will handle displaying tags in the post content and manage other post
type behaviour.
namespace FM\Teams;
class Teams
{
public function __construct()
{
add_action('init', [$this, 'initCPT']);
}
public function initCPT(): void
{
register_post_type('team', [
'labels' => [
'name' => __('Teams', 'fm'),
'singular_name' => __('Team', 'fm'),
],
'public' => true,
]);
}
}
namespace FM\Posts;
class Posts
{
public function __construct()
{
add_filter('the_content', [$this, 'addLinks']);
}
public function addLinks(string $content): string
{
$teams = get_posts([
'post_type' => 'team',
'posts_per_page' => 5,
]);
if (! empty($teams)) {
$links = [];
foreach ($teams as $team) {
$links[] = sprintf('<a hre="%s">%s</a>', get_permalink($team), get_the_title($team));
}
$content .= sprintf(__('Latest Teams: %s', 'fm'), join(', ', $links));
}
return $content;
}
}
Facade
Once I have modules, I need to connect them at some place called Facade. It acts as a central entry point for initializing and accessing system components, and delegating complex work to others. It simplifies setup, reduces code complexity, and provides a unified interface to interact with underlying systems.
namespace FM;
use FM\Posts\Posts;
use FM\Teams\Teams;
class App
{
public function __construct()
{
new Posts();
new Teams();
}
}
That's simple facade because it doesn't delegate any work yet, but I'll handle it in the next chapters. It's also important to add that you've just met the first design pattern. Sounds hard, but that's simple - right?
Keypoints
- Better approach for solving complex problems and building medium/large apps.
- Improves code readability by representing real-world objects and their interactions, making the code more intuitive and easier to understand or debug. It's easier for me to read
$team->hasName()
instead! empty(fm_team_get_name())
. - Ensures that internal details are not available publicly.
- Promotes reusable components, enhancing code maintainability and reducing redundancy. It makes the teamwork easier, because it allows to work on different parts of the code by many developers without struggling with conflicts.
Autoloading
One of the key points in this approach is separating responsibilities which result in more files to load. No worry, you won't need to dive into the files loading hell. Just check out the previous article and let the Composer do it for you!
Comparsion
Please note that the scores are subjective and base on my personal experience, habits and problems I was facing. It doesn't mean that one is always better than other.
Term | Procedural | OOP |
---|---|---|
Complexity How easy it is to feel confident and create code without a high level of technical knowledge | 4 | 3 |
Readability How easily the code can be understood and interpreted by the people. | 3 | 5 |
Modularity How easily smaller, independent components can be developed, modified, and maintained separately. | 2 | 5 |
Maintenance How eaisly the code can be modified and extended over time, minimizing errors and reducing the effort required for maintenance. | 3 | 4 |
Teamwork How eaisly the code can be managed by many developers at once. | 2 | 4 |
14/25 | 21/25 |
Usage
Let's assume that I just need to create Sport
CPT for further development.
- Using procedural code I would create another prefixed function in the general file.
- Using OOP I just need to create a new module and initizlize it in the facade. Note that
Teams
andPosts
haven't been touched at all.
namespace FM\Sports;
class Sports
{
public function __construct()
{
add_action('init', [$this, 'initCPT']);
}
public function initCPT(): void
{
register_post_type('sport', [
'labels' => [
'name' => __('Sports', 'fm'),
'singular_name' => __('Sport', 'fm'),
],
'public' => true,
]);
}
}
namespace FM;
use FM\Posts\Posts;
use FM\Sports\Sports;
use FM\Teams\Teams;
class App
{
public function __construct()
{
new Posts();
new Sports();
new Teams();
}
}
It's like connecting another LEGO structure with own behaviour to the whole building.
Which to use?
What YOU should choose? Let me help you answer this with a simple story.
The story about Mark
Mark, who is the founder of an IT company, in the early stages handled everything on his own - the development process, client relationships and financial aspects. It was pretty easy, because the company was small.
However, as the company grew, it became overwhelming so he found some open space and hired more people to handle different aspects of the business. Mark had less on his plate, and the company could handle more projects. With the new teams, things improved a little bit, but also other problems became to arising:
- Different teams started bothering each other, causing distractions. For example, when the finance team had a meeting to discuss rates where they screamed at each other making a mess, it disrupted other team members.
- Mark also promised full transparency so when Bill asked how much money Sarah earns, he just provided this information what caused problems in team engagement.
- And of course, he still had a lot on his mind, because he was directly responsible for each team member.
Mark started realising that the more people he have, the more new problems needed to be handled. He ran the company in a chaotic and centralized way. Everything depended on him so he decided that some changes are needed!
- First, he found a new office with separate rooms for each team and one meeting room. This separation allowed teams to work without unnecessary interruptions, creating a more productive environment.
- Second, he hired leaders for each team who were responsible for managing people and achieving the company's goals. Mark no longer needed to know every detail of each team's process. More important was if they achieved the required results.
- He also decided to reduce transparency in some aspects. Only authorized team members had access to sensitive financial information, ensuring a balance between trust and efficiency.
Decentralisation improved company efficiency and opened the doors for more challenging clients! Teams became self-sufficient, micromanagement was reduced, and problems started to become visible in the early stages which allowed react faster. When one team became unnecessary, Mark was able to close it without bothering others.
Is there any analogy?
Can you see the analogy of the story to two described programming methods? In my opinion it's just a story of transition from procedural to object-oriented coding.
- The first version was like writing procedural code, where everything was mixed together in global scope without clear boundaries. As the project grew and more people joined, it became chaotic and inefficient to work with. Simple changes in one place had impact on others. Working in the same place by several devs was also problematic.
- The second version introduced a more organized approach which was like writing object-oriented code. Creating teams was like creating modules with their own separated responsibilities and behavior. Leaders were like facades that delegates tasks to smaller units. Transparency reduction can be compared to encapsulation, where certain information is hidden to protect it.
What is the moral?
Mark started the company with growth in mind so decentralized and OOP approach was the best choice since beginning. Building a business would take more time initially, but it would reduce the cost of change in long term. And that's what I like.
It can be compared to our clients. They come to us and invest money in solutions that will let them GROW. I've never had a client who said that they pay such an amount of money just to sell the same number of products as now. It doesn't make sense. That's why I believe that one of the most certain things in business is CHANGE.
So even when I don't plan to rule the world in the first week of running a product, I'm sure that at some point it should change. So if I can be better prepared to this from the beginning, I'll take this. That's why I'm choosing the OOP approach by default. It makes me better prepared for change.
What should you use?
I recommend OOP approach by default because it fits my needs, but when it comes to you, I don't really know! You should decide! 😂 Ok, ok, I was already talking that such answer doesn't bring any value, so check out the summary!
Summary
Both approaches have their pros and cons, and the decision ultimately rests on your own preferences and current needs. Regardless of which option you choose, it's important to understand why you choose this particular one.
- If you believe that you will always be the sole decision-maker and won't be working in a team, or if you're working on a simple project where advanced architecture isn't necessary, then the procedural approach might be the right fit for you. It offers simplicity and straightforwardness.
- On the other hand, if you're working on a product that will have a wide user base and a long lifespan, or if you plan to build a larger team to handle the project over time, then investing time in the object-oriented approach could be worth. It provides a structured and scalable solution.
If you still don't know, put yourself in Mark's shoes and answer the following question:
Do I expect problems that may force me to make changes to my process, as Mark did?
If so - then use OOP ;)