Observer Pattern Logic Without OOP? - php

I was thinking about implementing a logic similar to observer pattern on my website, for implementing hooks.
What I am looking for is something similar to this Best way to allow plugins for a PHP application
However the code there is too limited, as I cant attach multiple hooks to same listener.
I am clueless about how to amplify that code to make it able to listen multiple actions at one event.
Thank You

You can do as ircmaxell suggests: add hooks. But clearly, the information he gave was not enough for you.
If you like learning by example, you may look at the CMS Drupal, wich is not OOP, but uses the observer pattern, called hooks all over the place to allow a modular design.
A hook works as follows:
a piece of php looks for the existence of a specially named function.
If that exists, call it and use its output (or do nothing with it)
For example:
Just before an article gets saved in Drupal, the article-system calls the hook_insert
Every module that has a function in the name of ModuleName_insert, will see that function being called. Example: pirate.module may have a function pirate_insert(). The article system makes a roundtrip along all the modules and sees if ModuleName_insert exists. It will pass by pirate module and finds pirate_insert(). It will then call that function (and pass some arguments along too). As such, allowing the pirate.module to change the article just before insertation (or fire some actions, such as turning the body-text into pirate-speek).
The magic happens in so called user_callbacks. An example:
$hook = 'insert'
foreach (module_implements($hook) as $module) {
$function = $module .'_'. $hook;
$result = call_user_func_array($function, $args);
}
And the function module_implements might look something like:
$list = module_list(FALSE, TRUE, $sort); //looks for files that are considered "modules" or "addons".
foreach ($list as $module) {
if (function_exists($module.'_'.$hook)) { //see if the module has the hook 'registered'
$implementations[$hook][] = $module; //if so: add it to a list with functions to be called.
}
}

Simply add a ' * ' hook, and modify the hook() function to call all the 'hooks' in both the named event and the ' * ' event.
Then, simply do:
add_listener('*', 'mycallback');

Take a look at Spl_Observer.
You said you didn't want OOP, but you can easily implement a non-OOP wrapper around this.

Related

How can apply_filters be used to create a filter hook in wordpress?

Wordpress documentation about apply_filters mentions:
It is possible to create new filter hooks by simply calling this function, specifying the name of the new hook using the $hook_name parameter.
Ok, I want to create a filter hook called the_content2, so I do what the docs suggest:
$custom_hook = apply_filters("the_content2", the_content());
Now I assume the_content2 hook is created and is similar to the_content. So on a single post page I should be able to use the_content2 hook like the_content2:
the_content2()
This throws error, function is not defined. I am beginner in wordpress, can someone please explain comprehensively how apply_filters can be used to create your own filters.
Hooks (actions and filters) have two main components, the part that declares the hook, and the callbacks that implement the hook.
A very simple example is:
$name = "Chris";
$name = apply_filters("change_name", $name);
echo $name;
If no one registers a callback, then Chris will be outputted. However, if someone writes this:
add_filter(
"change_name",
function($name){ return "Steve";}
);
Then Steve will be outputted.
At no point, however, will a new function called change_name() be created.
As it relates to hooks, I would generally steer you away from thinking about a hook "being created", and instead think about it as "being called". A function always exists, but hooks are only used if they are actually called.
To your main issue as it relates to the_content(), I would determine what you want to do with that. If you want to transform the result of that function for all instances, a hook is probably a good idea. However, if you just want to use it differently sometimes, a custom function (without a hook) that calls the original, does something and returns it might be more appropriate.
EDIT
Below is a mock of how the hook system works. I've simplified it a bunch, but it should give you a gist of how it works.
function add_filter($name, $callback) {
global $filters;
$filters[$name][] = $callback;
}
function apply_filters($name, $value) {
global $filters;
if(array_key_exists($name, $filters)) {
foreach($filters[$name] as $callback) {
$value = $callback($value);
}
}
return $value;
}
I can't stress this enough, no one is really declaring or registering things. At most, an array key is added with a name when the add_filter/add_action is used. Then, if someone calls the apply_filters/do_action then the key is looked up, and if it exists, the callbacks are run.
So add_filter/add_action has to happen first. It is "I'm interested in XYZ if it actually happens". There's many cases where it might not happen, but if it does, then the callback will fire.
The apply_filters can't/shouldn't throw an error if no one registered a callback, because that is totally a valid case. I often write code that is extensible for future callers but you'll notice that if no one adds a filter, the initial value is returned.

Plugin system with events or hooks?

I've created a plugin system for a software in php. In order for a plugin to alter the behaviour of the programm I wrote this (simplified) code:
class PluginController {
/* ... */
public function addHook($name, $function, $priority = 10) {
/* store the function callback $function associated with $name */
}
public function executeHook($name, $args = array()) {
/* execute all function callbacks associated with $name
* in order of their priority and return their results */
}
}
So plugins can add callbacks using addHook and somewhere in the application these callbacks get executed by calling executeHook.
This works quite well, but after reading some time about the topic, I'm still unsure if this technique is a event- or a hook- system.
Some sources say the difference has to do with loose and tight coupling.
Others say that hooks has return values, and events not. And others again say that events are for handlig asynchronous activity, and hooks just to inject code at some point.
So again, is the above code about events or hooks, and can someone explain the difference?
Your code is more like an Event.
Hooks allow a plugin to interact with the code that called it. They are called with the assumption that data will be returned, and the originating code will usually loop through the returned data immediately after calling the hook.
Events, on the other hand, are only called to announce when a particular action has taken place. They give plugins an opportunity to run their own event-handling logic at that point, without directly affecting the originating code in any way.
Source

Get all "pages" in YII?

I'm trying to create my own xml sitemap. Everything is done except for the part that I thought was going to be the easiest. How do you get a list of all the pages on the site? I have a bunch of views in a /site folder and a few others. Is there a way to explicitly request their URLs or perhaps via the controllers?
I do not want to make use of an extension
You can use reflection to iterate through all methods of all your controllers:
Yii::import('application.controllers.*');
$urls = array();
$directory = Yii::getPathOfAlias('application.controllers');
$iterator = new DirectoryIterator($directory);
foreach ($iterator as $fileinfo)
{
if ($fileinfo->isFile() and $fileinfo->getExtension() == 'php')
{
$className = substr($fileinfo->getFilename(), 0, -4); //strip extension
$class = new ReflectionClass($className);
foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method)
{
$methodName = $method->getName();
//only take methods that begin with 'action', but skip actions() method
if (strpos($methodName, 'action') === 0 and $methodName != 'actions')
{
$controller = lcfirst(substr($className, 0, strrpos($className, 'Controller')));
$action = lcfirst(substr($methodName, 6));
$urls[] = Yii::app()->createAbsoluteUrl("$controller/$action");
}
}
}
}
You need to know what content you want to include in your sitemap.xml, I don't really think you want to include ALL pages in your sitemap.xml, or do you really want to include something like site.com/article/edit/1 ?
That said, you may only want the result from the view action in your controllers. truth is, you need to know what you want to indexed.
Do not think in terms of controllers/actions/views, but rather think of the resources in your system that you want indexed, be them articles, or pages, they are all in your database or stored somehow, so you can list them, and they have a URI that identifies them, getting the URI is a matter of invoking a couple functions.
There are two possiblities -
Case 1:
You are running a static website then you can find all your HTML inside 1 folder - protected/views/site/pages
http://www.yiiframework.com/wiki/22/how-to-display-static-pages-in-yii/
Case 2:
Website is dynamic. Tasks such as generating and regenerating Sitemaps can be classified into background tasks.
Running background taks can be achieved by emulating the browser which is possible in linux using - WGET, GET or lynx commands
Or, You can create a CronController as a CConsoleCommand. How to use Commands in YII is shown in link below -
http://tariffstreet.com/yii/2012/04/implementing-cron-jobs-with-yii-and-cconsolecommand/
Sitemap is an XML which lists your site's URL. But it does more than that.
It helps you visualize the structure of a website , you may have
category
subcategories.
While making a useful extension, above points can be kept into consideration before design.
Frameworks like Wordpress provide way to generate categorical sitemap.
So the metadata for each page is stored from before and using that metadata it discovers and group pages.
Solution by Reflection suggested by #Pavle is good and should be the way to go.
Consider there may be partial views and you may or may not want to list them as separate links.
So how much effort you want to put into creating the extension is subject to some of these as well.
You may either ask user to list down all variables in config fie and go from there which is not bad or you have to group pages and list using some techniques like reflection and parsing pages and looking for regex.
For ex - Based on module names you can group them first and controllers inside a module can form sub-group.
One first approach could be to iterate over the view files, but then you have to take into account that in some cases, views are not page destinations, but page sections included in another pages by using CController::renderPartial() method. By exploring CController's Class Reference I came upon the CController::actions() method.
So, I have not found any Yii way to iterate over all the actions of a CController, but I used php to iterate over all the methods of a SiteController in one of my projects and filter them to these with the prefix 'action', which is my action prefix, here's the sample
class SiteController extends Controller{
public function actionTest(){
echo '<h1>Test Page!</h1></p>';
$methods = get_class_methods(get_class($this));
// The action prefix is strlen('action') = 6
$actionPrefix = 'action';
$reversedActionPrefix = strrev($actionPrefix);
$actionPrefixLength = strlen($actionPrefix);
foreach ($methods as $index=>$methodName){
//Always unset actions(), since it is not a controller action itself and it has the prefix 'action'
if ($methodName==='actions') {
unset($methods[$index]);
continue;
}
$reversedMethod = strrev($methodName);
/* if the last 6 characters of the reversed substring === 'noitca',
* it means that $method Name corresponds to a Controller Action,
* otherwise it is an inherited method and must be unset.
*/
if (substr($reversedMethod, -$actionPrefixLength)!==$reversedActionPrefix){
unset($methods[$index]);
} else $methods[$index] = strrev(str_replace($reversedActionPrefix, '', $reversedMethod,$replace=1));
}
echo 'Actions '.CHtml::listBox('methods', NULL, $methods);
}
...
}
And the output I got was..
I'm sure it can be furtherly refined, but this method should work for any of the controllers you have...
So what you have to do is:
For each Controller: Filter out all the not-action methods of the class, using the above method. You can build an associative array like
array(
'controllerName1'=>array(
'action1_1',
'action1_2'),
'controllerName2'=>array(
'action2_1',
'action2_2'),
);
I would add a static method getAllActions() in my SiteController for this.
get_class_methods, get_class, strrev and strlen are all PHP functions.
Based on your question:
1. How do you get a list of all the pages on the site?
Based on Yii's way of module/controller/action/action_params and your need to construct a sitemap for SEO.
It will be difficult to parse automatically to get all the urls as your action params varies indefinitely. Though you could simply get controller/action easily as constructed by
Pavle Predic. The complexity comes along when you have customized (SERF) URL rules meant for SEO.
The next best solution is to have a database of contents and you know how to get each content via url rules, then a cron console job to create all the urls to be saved as sitemap.xml.
Hope this helps!

How does plugin system work (wordpress, mybb ...)?

I'm curious how plugins work, I just know that instead of changing the code we use plugins, but how do they do their job without changing the code ? and what should a coder consider when coding a new project so it can have plugins ? and thank you very much :D
There are multiple variations on how to implement a plugin system. Wordpress uses a quite common scheme often described as "hooks." I don't know the exact implementation but it basically works like this:
// plugin.php script registers its own callback function
register_plugin("hook_type", "plugin_function_123");
function plugin_function_123($params) { ... }
Where the hook_type is often an action name or something. And when the main application runs through a specific point (or e.g. needs some data processsed) it invokes all registered callback functions:
$output = call_plugins("hook_type", $param1, $param2);
This is often implemented behind the scenes as a simple loop:
foreach ($registered_plugins[$action] as $func) {
$func($param1, $param2, ...); // or call_user_func_
}
Now it depends on the hook/action type what parameters are present, and if any result text is expected. There are also differences in parameter passing (e.g. some callbacks require &$var references). And some plugin systems rely on objects instead (if not as many varying action types exist or more complex structures are to be worked with).

How to make a PHP script that's pluginable?

I'm creating my own script using the CodeIgniter MVC framework. Now, i want users to easily modify the site functionality and adding their own without modifying the code which i've already written.
How do i make my site pluginable ?
EDIT: The users would be the site admins. Not the end user. Basically just like drupal or joomla. Want the admin to be able to create/add plugins to extend site functionality.
There may be a better way that's specific to CodeIgniter, but this is what I would do:
First, create functions for various "hook points" in your code. Say, a function named PreArticle that you call in your code, before an article is displayed.
Allow the user to write code like this:
addHook_PreArticle('funcToCall');
function funcToCall( &$articleText ) {
$articleText = str_replace('Hello', 'World', $articleText);
}
addHook_PreArticle is a function you've defined, which would add the passed string to some internal list. Then when the PreArticle function is called, each of those functions are executed, passing in any appropriate parameters that you define.
Many CMS's Like Joomla and Blogs like Wordpress use variable function names:
$function="phpinfo";
$function();
You could load this into an array to create a list of functions that can be overridden.
That's a perfect case to use the Observer Pattern.
http://devzone.zend.com/article/5

Categories