I have a question about IOC and when I don't know the class to be instantiated at run-time. For example, I have a few types of View classes. (HtmlView, CsvView, PDFView, etc ) that implement my view interface. The type of view class that I need is determined by user input ( a string in the DB ). I am leaning to using a ViewFactory class that has a make method, the problem is that this will be hiding the View dependency because I only need a ViewFactory.
class ViewFactory{
public function make($viewType){
if($viewType == 'html'){
$view = new HtmlView();
}
// ...
return $view
}
}
class ReportGenerator{
public function constructor(Array $data, ViewFactory $viewFactory, String $viewType ){
$this->data = $data;
$this->view = $viewFactory($viewType);
}
public function generate(){
return $this->view->render($this->data)
}
}
It seems to me unclear that ReportGenerator depends on a base ViewInterface. Is there a better way, without using a static method.
interface ViewInterface {
public function render();
}
class HtmlView implements ViewInterface {
const HTML_VIEW = 'html';
public function render() {
// TODO: Implement render() method.
}
}
class AnotherView implements ViewInterface {
const ANOTHER_VIEW = 'another';
public function render() {
// TODO: Implement render() method.
}
}
class ViewFactory {
public static function make($viewType) { // this could also be static
// or use a construct, whichever strikes your fancy
if ('html' == $viewType) { // fan of Yoda metod, I am
$view = new HtmlView();
}
// ...
return $view;
}
}
class ReportGenerator {
private $data;
private $view;
public function constructor(array $data, ViewInterface $view) {
$this->data = $data;
$this->view = $view;
}
public function generate() {
return $this->view->render($this->data);
}
}
$view = ViewFactory::make(HtmlView::HTML_VIEW);
$report = new ReportGenerator([], $view);
Instead of letting the ReportGenerator deal with anything view related, which it must never do, simply pass in the already created view.
The view itself should be created outside of ReportGenerator(or any other class for that matter - except the ViewFactory).
In the ViewFactory you can always add other methods to deal with the logic of creating new views.
The magical __call() and __callStatic can pretty much handle any non existing method on the class, but is there a way to handle a non existing magical method on a class?!
Here's an example on why I need this:
I have a class called DoSomething:
class DoSomething{
public function ok(){
echo 'Something!';
}
}
I want to call this class as a function for a reason! which should call the __invoke function of that class:
$doSomething = new DoSomething();
$doSomething();
Normally by doing that, the class should look for the __invoke function, however in my case I don't to have that function declared on my class (DoSomething), instead I want to be able to call another function (such as the ok()) if the __invoke doesn't exist.
I was expecting something like this to work, but of course it didn't :)
public function __call($class, $arguments)
{
$object = IoC::resolve($class);
$object->ok(...$arguments);
}
The main goal is to use the class as a function, without having to declare the __invoke method. Handle the function does not exist error and call another function instead.
I think that would be really cool :D I appreciate suggestions or other solutions to achieve this.
Internal solution
Extract an abstract class
You could extract an abstract class and have your classes extend it:
<?php
abstract class Invokable
{
public function __invoke()
{
return $this->ok();
}
abstract public function ok();
}
class DoSomething extends Invokable
{
public function ok()
{
echo 'Something';
}
}
$doSomething = new DoSomething();
echo $doSomething();
For an example, see:
https://3v4l.org/m0ih8
Extract a trait
You could extract a trait and have your classes use it:
<?php
trait InvokableTrait
{
public function __invoke()
{
return $this->ok();
}
}
class DoSomething
{
use InvokableTrait;
public function ok()
{
echo 'Something';
}
}
$doSomething = new DoSomething();
echo $doSomething();
For an example, see:
https://3v4l.org/ftUfI
External Solution
Create a proxy
You could create a proxy (a decorator) that composes the object that is not invokable:
<?php
class InvokableDecorator
{
private $decorated;
public function __construct($decorated)
{
$this->decorated = $decorated;
}
public function __call($name, $arguments)
{
/**
* delegate to decorated object if the method exists
*/
if (method_exists($this->decorated, $name)) {
return $this->decorated->{$name}($arguments);
}
}
public function __invoke()
{
return $this->decorated->ok();
}
}
class DoSomething
{
public function ok()
{
echo 'Something';
}
}
$doSomething = new InvokableDecorator(new DoSomething());
echo $doSomething();
For an example, see:
https://3v4l.org/C3XEX
Create a handler
You could create a handler that takes care of determining this externally:
<?php
class Handler
{
public function handle($subject)
{
if (is_callable($subject)) {
return $subject();
}
if (method_exists($subject, 'ok')) {
return $subject->ok();
}
throw new \BadMethodCallException(sprintf(
'Unable to handle instance of "%s"',
get_class($subject)
));
}
}
class DoSomething
{
public function ok()
{
echo 'Something';
}
}
$handler = new Handler();
echo $handler->handle(new DoSomething());
For an example, see:
https://3v4l.org/E0NVs
I have the simplest bit of code :
Interface
interface iCrudRepository{
public function Create($id);
public function Read($id);
public function Update($id);
public function Delete($id);
}
Parent
class Repository
{
function __construct()
{
echo "SHOULD NOT BE CALLED AUTOMATICALLY";
}
}
Class
require_once(__DIR__.'/../injection/bootstrap.php');
class Admin extends Repository implements iCrudRepository
{
function Create($id)
{
}
function Read($id)
{
}
function Update($id)
{
}
function Delete($id)
{
}
}
$admin = new Admin();
$admin->Create("Something");
The bootstrap class autoloads my classes via the spl_autoload_register function. Since in the Admin class I don't call the parent constructor, it shouldn't execute what is in the parent's constructor right?
The Output
SHOULD NOT BE CALLED AUTOMATICALLY
Probably missing something obvious here but can't quite figure out why it is called.
Docs state:
Parent constructors are not called implicitly if the child class
defines a constructor.
So you have to do this in order to prevent what you are seeing:
class Admin extends Repository implements iCrudRepository
{
public function __construct()
{
}
function Create($id)
{
}
function Read($id)
{
}
function Update($id)
{
}
function Delete($id)
{
}
}
For deleteBarDocument(), I wish not to implement
$this->getModel()->updateParentAudit($this->audit_table,$_POST['id']);
in the deleteDocumentHelper() helper method, but implement something else such as
$this->bla($_POST['id']);
$this->blabla($_POST['id'],$_POST['doc_id']);
How can I extend deleteDocumentHelper() for deleteBarDocument(), but leave it as is for deleteFooDocument()?
Normally, I would just replace
$this->getModel()->updateParentAudit($this->audit_table,$_POST['id']);
with
$this->doSomething();
and create a doSomething() method with whatever code is needed. The probably with this approach is it will affect both deleteFooDocument() and deleteBarDocument() which is not desired.
Or should I pass the helper method an anonymous function? I am cautious of doing so as I've been warned that anonymous function should be used sparingly.
<?php
class parentController
{
protected function deleteDocumentHelper($type){
if(isset($_POST['id'],$_POST['doc_id'])){
if(documents::removeDocument($type,$_POST['doc_id'],$_POST['id']))
{
$success=1;
//Ability to replace the following line with one or more lines
$this->getModel()->updateParentAudit($this->audit_table,$_POST['id']);
}
else {$success=0;}
header('Content-Type: application/json;');
$this->dontCache();
echo(json_encode(array('success'=>$success)));
}
else {exit($this->missingPage());}
}
}
class childController extends parentController
{
public function deleteFooDocument(){$this->deleteDocumentHelper('foo');}
public function deleteBarDocument(){$this->deleteDocumentHelper('bar');}
}
//Application creates childController object, and evokes apprpropriate method based on GET/POST
?>
You say:
Normally, I would just replace
$this->getModel()->updateParentAudit($this->audit_table,$_POST['id']);
with
$this->doSomething();
and create a doSomething() method with whatever code is needed. The probably with this approach is it will affect both deleteFooDocument() and deleteBarDocument() which is not desired.
But, why not in following way?
class parentController
{
protected function doSomething()
{
$this->getModel()->updateParentAudit($this->audit_table,$_POST['id']);
}
protected function deleteDocumentHelper($type){
if(isset($_POST['id'],$_POST['doc_id'])){
if(documents::removeDocument($type,$_POST['doc_id'],$_POST['id']))
{
$success=1;
//Ability to replace the following line with one or more lines
$this->doSomething();
}
(...)
}
else {exit($this->missingPage());}
}
}
class childController extends parentController
{
private $somethingElse = False;
protected function doSomething()
{
if( $this->somethingElse )
{
// Your deleteBar code
}
else
{
parent::doSomething();
}
}
protected function deleteDocumentHelper( $type, $somethingElse=False )
{
$this->somethingElse = $somethingElse;
parent::deleteDocumentHelper( $type );
}
public function deleteFooDocument(){$this->deleteDocumentHelper('foo');}
public function deleteBarDocument(){$this->$this->deleteDocumentHelper('bar',1);}
}
With this approach you can substantially leave untouched the parent class, and apply different code(s) only when needed.
Is it possible to use the equivalent for .NET method attributes in PHP, or in some way simulate these?
Context
We have an in-house URL routing class that we like a lot. The way it works today is that we first have to register all the routes with a central route manager, like so:
$oRouteManager->RegisterRoute('admin/test/', array('CAdmin', 'SomeMethod'));
$oRouteManager->RegisterRoute('admin/foo/', array('CAdmin', 'SomeOtherMethod'));
$oRouteManager->RegisterRoute('test/', array('CTest', 'SomeMethod'));
Whenever a route is encountered, the callback method (in the cases above they are static class methods) is called. However, this separates the route from the method, at least in code.
I am looking for some method to put the route closer to the method, as you could have done in C#:
<Route Path="admin/test/">
public static void SomeMethod() { /* implementation */ }
My options as I see them now, are either to create some sort of phpDoc extension that allows me to something like this:
/**
* #route admin/test/
*/
public static function SomeMethod() { /* implementation */ }
But that would require writing/reusing a parser for phpDoc, and will most likely be rather slow.
The other option would be to separate each route into it's own class, and have methods like the following:
class CAdminTest extends CRoute
{
public static function Invoke() { /* implementation */ }
public static function GetRoute() { return "admin/test/"; }
}
However, this would still require registering every single class, and there would be a great number of classes like this (not to mention the amount of extra code).
So what are my options here? What would be the best way to keep the route close to the method it invokes?
This is how I ended up solving this. The article provided by Kevin was a huge help. By using ReflectionClass and ReflectionMethod::getDocComment, I can walk through the phpDoc comments very easily. A small regular expression finds any #route, and is registered to the method.
Reflection is not that quick (in our case, about 2,5 times as slow as having hard-coded calls to RegiserRoute in a separate function), and since we have a lot of routes, we had to cache the finished list of routes in Memcached, so reflection is unnecessary on every page load. In total we ended up going from taking 7ms to register the routes to 1,7ms on average when cached (reflection on every page load used 18ms on average.
The code to do this, which can be overridden in a subclass if you need manual registration, is as follows:
public static function RegisterRoutes()
{
$sClass = get_called_class(); // unavailable in PHP < 5.3.0
$rflClass = new ReflectionClass($sClass);
foreach ($rflClass->getMethods() as $rflMethod)
{
$sComment = $rflMethod->getDocComment();
if (preg_match_all('%^\s*\*\s*#route\s+(?P<route>/?(?:[a-z0-9]+/?)+)\s*$%im', $sComment, $result, PREG_PATTERN_ORDER))
{
foreach ($result[1] as $sRoute)
{
$sMethod = $rflMethod->GetName();
$oRouteManager->RegisterRoute($sRoute, array($sClass, $sMethod));
}
}
}
}
Thanks to everyone for pointing me in the right direction, there were lots of good suggestions here! We went with this approach simply because it allows us to keep the route close to the code it invokes:
class CSomeRoutable extends CRoutable
{
/**
* #route /foo/bar
* #route /for/baz
*/
public static function SomeRoute($SomeUnsafeParameter)
{
// this is accessible through two different routes
echo (int)$SomeUnsafeParameter;
}
}
Using PHP 5.3, you could use closures or "Anonymous functions" to tie the code to the route.
For example:
<?php
class Router
{
protected $routes;
public function __construct(){
$this->routes = array();
}
public function RegisterRoute($route, $callback) {
$this->routes[$route] = $callback;
}
public function CallRoute($route)
{
if(array_key_exists($route, $this->routes)) {
$this->routes[$route]();
}
}
}
$router = new Router();
$router->RegisterRoute('admin/test/', function() {
echo "Somebody called the Admin Test thingie!";
});
$router->CallRoute('admin/test/');
// Outputs: Somebody called the Admin Test thingie!
?>
Here's a method which may suit your needs. Each class that contains routes must implement an interface and then later loop through all defined classes which implement that interface to collect a list of routes. The interface contains a single method which expects an array of UrlRoute objects to be returned. These are then registered using your existing URL routing class.
Edit: I was just thinking, the UrlRoute class should probably also contain a field for ClassName. Then $oRouteManager->RegisterRoute($urlRoute->route, array($className, $urlRoute->method)) could be simplified to $oRouteManager->RegisterRoute($urlRoute). However, this would require a change to your existing framework...
interface IUrlRoute
{
public static function GetRoutes();
}
class UrlRoute
{
var $route;
var $method;
public function __construct($route, $method)
{
$this->route = $route;
$this->method = $method;
}
}
class Page1 implements IUrlRoute
{
public static function GetRoutes()
{
return array(
new UrlRoute('page1/test/', 'test')
);
}
public function test()
{
}
}
class Page2 implements IUrlRoute
{
public static function GetRoutes()
{
return array(
new UrlRoute('page2/someroute/', 'test3'),
new UrlRoute('page2/anotherpage/', 'anotherpage')
);
}
public function test3()
{
}
public function anotherpage()
{
}
}
$classes = get_declared_classes();
foreach($classes as $className)
{
$c = new ReflectionClass($className);
if( $c->implementsInterface('IUrlRoute') )
{
$fnRoute = $c->getMethod('GetRoutes');
$listRoutes = $fnRoute->invoke(null);
foreach($listRoutes as $urlRoute)
{
$oRouteManager->RegisterRoute($urlRoute->route, array($className, $urlRoute->method));
}
}
}
I'd use a combination of interfaces and a singleton class to register routes on the fly.
I would use a convention of naming the router classes like FirstRouter, SecondRouter and so on. This would enable this to work:
foreach (get_declared_classes() as $class) {
if (preg_match('/Router$/',$class)) {
new $class;
}
}
That would register all declared classes with my router manager.
This is the code to call the route method
$rm = routemgr::getInstance()->route('test/test');
A router method would look like this
static public function testRoute() {
if (self::$register) {
return 'test/test'; // path
}
echo "testRoute\n";
}
The interfaces
interface getroutes {
public function getRoutes();
}
interface router extends getroutes {
public function route($path);
public function match($path);
}
interface routes {
public function getPath();
public function getMethod();
}
And this is my definition av a route
class route implements routes {
public function getPath() {
return $this->path;
}
public function setPath($path) {
$this->path = $path;
}
public function getMethod() {
return $this->method;
}
public function setMethod($class,$method) {
$this->method = array($class,$method);
return $this;
}
public function __construct($path,$method) {
$this->path = $path;
$this->method = $method;
}
}
The Router manager
class routemgr implements router {
private $routes;
static private $instance;
private function __construct() {
}
static public function getInstance() {
if (!(self::$instance instanceof routemgr)) {
self::$instance = new routemgr();
}
return self::$instance;
}
public function addRoute($object) {
$this->routes[] = $object;
}
public function route($path) {
foreach ($this->routes as $router) {
if ($router->match($path)) {
$router->route($path);
}
}
}
public function match($path) {
foreach ($this->routes as $router) {
if ($router->match($path)) {
return true;
}
}
}
public function getRoutes() {
foreach ($this->routes as $router) {
foreach ($router->getRoutes() as $route) {
$total[] = $route;
}
}
return $total;
}
}
And the self register super class
class selfregister implements router {
private $routes;
static protected $register = true;
public function getRoutes() {
return $this->routes;
}
public function __construct() {
self::$register = true;
foreach (get_class_methods(get_class($this)) as $name) {
if (preg_match('/Route$/',$name)) {
$path = call_user_method($name, $this);
if ($path) {
$this->routes[] = new route($path,array(get_class($this),$name));
}
}
}
self::$register = false;
routemgr::getInstance()->addRoute($this);
}
public function route($path) {
foreach ($this->routes as $route) {
if ($route->getPath() == $path) {
call_user_func($route->getMethod());
}
}
}
public function match($path) {
foreach ($this->routes as $route) {
if ($route->getPath() == $path) {
return true;
}
}
}
}
And finally the self registering router class
class aRouter extends selfregister {
static public function testRoute() {
if (self::$register) {
return 'test/test';
}
echo "testRoute\n";
}
static public function test2Route() {
if (self::$register) {
return 'test2/test';
}
echo "test2Route\n";
}
}
the closest you can put your path to the function definition (IMHO) is right before the class definition. so you would have
$oRouteManager->RegisterRoute('test/', array('CTest', 'SomeMethod'));
class CTest {
public static function SomeMethod() {}
}
and
$oRouteManager->RegisterRoute('admin/test/', array('CAdmin', 'SomeMethod'));
$oRouteManager->RegisterRoute('admin/foo/', array('CAdmin', 'SomeOtherMethod'));
class CAdmin {
public static function SomeMethod() {}
public static function SomeOtherMethod() {}
}
There is a proposal for this, it was declined. See the rfc here:
Attributes RFC at php.net
My solution to this desire is something like this:
abstract class MyAttributableBase{
protected static $_methodAttributes=[];
public static function getMethodAtributes(string $method):?array{
if( isset(self::$_methodAttributes[$method])){
return self::$_methodAttributes[$method];
}
return null;
}
protected static function setMethodAttributes(string $method, array $attrs):void{
self::$_methodAttributes[$method] = $attrs;
}
}
class MyController extends MyAttributableBase{
protected static function getMethodAtributes(string $method):?array{
switch( $method ){
case 'myAction':
return ['attrOne'=>'value1'];
default:
return parent::getMethodAttributes($method);
}
}
}
Usage:
$c = new MyController();
print $c::getMethodAttributes('myAction')['attrOne'];
You can of course use it from within a base class method to do "routing" stuff in this case, or from a routing class that operates on "MyAttributableBase" objects, or anywhere else you would want to inspect this attached metadata for any purpose. I prefer this "in-code" solution to using phpDoc. Note I didn't attempt to test this exact code but it is mentally copied from a working solution. If it doesn't compile for some small reason it should be easy to fix and use. I have not figured out a way to cleanly put the attributes near the method definition. Using this implementation in the base you COULD set the attributes within the method (myAction in this case) as the first code to execute, but it would not be a static attribute, it would get reset at each invocation. You could add code to additionally ensure it is only set once but that's just extra code to execute and maybe is not better. Overriding the get method allows you to set the info once and refer to it once, even though it's not that close to the method definition. Keeping the static array in the base does allow some flexibility if there are cases for adding or changing metadata at runtime. I could be possible to use something like phpDoc and a static constructor to parse that when the first class is created to populate the static metadata array. I haven't found a solution that is awesome but the one I'm using is adequate.