Let's say we have few elements:
1) Simple PHP Class
<?php
class page {
function display() {
// Display widgets implementing widget interface
}
}
?>
2) Interface for Widget Classes
<?php
interface Widget {
function print();
}
?>
3) And few classes implementing the Widget interface
<?php
class Poll implements Widget {
function print() {
echo "I'm a poll";
}
}
class Form implements Widget {
function print() {
echo "I'm a form";
}
}
?>
What is the right way taking in mind SOLID PHP OOP logic to automatically run print() function in all classes implementing Widget interface? Let's assume all the files are already included.
Basically, whenever new class implementing Widget is created, it's response should be printed out in a page.
Maybe all my logic behind this is wrong?
The only way I could think of without using any config or database is to get all declared classes and check the ones that implement the Widget
interface:
This is the modified Page
class
class Page {
public $widgets = [];
public function addWidget(Widget $widget)
{
$this->widgets[] = $widget;
}
public function display()
{
// Now you have an array of Widgets you can loop through them
// And run print()
foreach ($this->widgets as $widget) {
$widget->print();
}
}
}
And then we can get all classes (that are loaded) that implement the Widget
interface:
$widgets = array_filter(
get_declared_classes(),
function ($className) {
return in_array('Widget', class_implements($className));
}
);
And then we pass them to Page
:
$page = new Page();
foreach ($widgets as $item) {
$widget = new $item;
$page->addWidget($widget);
}
// Now Page has all Widgets we can call display()
$page->display();
But be aware that get_declared_classes()
will return a lot of classes (160 in my PC), so we are looping through them all and checking if each one implements the Widget
interface.
Now whenever you have a class that implements the Widget
interface it will be used by Page
. To be honest I don't know if there is a better way so it might be worth it to wait for other answers.
You can always use a little bit of magic.
Let's say you have all widgets in one directory. You can fetch them with some filesystem operations, iterate over the results, verify whether it is an instance of Widet, create new instance and run it.
This solution has one big benefit. You write this code once and each new widget will be automatically included and run. This solves the open close principle :)
But it also has some cons:
Another solution is to have a class like WidgetCollection which will know all the widget in the system and it can provide you them. So when you will create a new widget implementation you will have to add it to the collection, that's all :)
Cheers.
I can't really say if it adheres to SOLID principles but I can think of two approaches:
Store widgets in a directory and use PSR-4 autoloading. Then use file system functions to scan the directory and create instances of class names that correspond to file names. (Make sure you have full control on directory contents or you'll be hacked!)
// Totally untested code with no error checking
foreach (glob(YOUR_BASE_DIR_HERE . '/Widget/*.php') as $file) {
$className = 'Widget\\' . basename($file, '.php');
$widget = new $className();
$widget->print();
}
This assumes that widgets do not need to be configured thus instance creation can be automated.
Tweak the widget spec so that each file returns a properly initialised instance and include
it (same warnings apply):
foreach (glob(YOUR_WIDGETS_DIR_HERE . '/*.php') as $file) {
$widget = include $file;
$widget->print();
}
… where every included file ends with something like return new Poll();
.
It's important that included files do not pollute the global scope with variables. Depending on how complex widgets are expected to be, you may want to have actual class definitions elsewhere so you adhere to PSR-1 coding standard: Files SHOULD either declare symbols (classes, functions, constants, etc.) or cause side-effects (e.g. generate output, change .ini settings, etc.) but SHOULD NOT do both.