I have to create a variable that is callable with php's is_callable
I have done this:
$callable = array(new MyClass, 'methodName');
But I want to pass a parameter to the method.
How can I do that?
Cause using symfony's event dispatcher component will be like:
$sfEventDispatcher->connect('log.write', array(new IC_Log('logfile.txt'), 'write'));
The first parameter is just a event name, the second is the callable variable.
But I can only call the write method, I want to pass a parameter to it.
Since PHP 5.3 you can use anonymous functions.
You should connect the listener like this:
$dispatcher->connect('my.event', function($event) {
$my_class = new MyClass;
$my_class->myMethod($event, array('my_param' => 'my_value'));
});
Then you will be able to get the parameters array in the listener:
class MyClass {
public function myMethod(sfEvent $event, $parameters) {
$my_value = $parameters['my_param'];
$event_param_value = $event['event_param'];
}
}
Now you can notify the event normally:
$dispatcher->notify(new sfEvent($this, 'my.event', array('event_param' => 'event_param_value')));
Take care that this listener can't be disconnected.
If you need to disconnect it, put the anonymous function in a variable:
$dispatcher->connect('my.event', $my_listener = function($event) {
$my_class = new MyClass;
$my_class->myMethod($event, array('my_param' => 'my_value'));
});
You should be able to disconnect with:
$dispatcher->disconnect('my.event', $my_listener);
You don't pass parameters to your listener callbacks (without extending the core). Symfony will be the one calling it and will pass an event object. If you need additional info, you can create a different method that calls another method where you can control the parameters.
$callable1 = array(new MyWriter, 'write1');
$callable2 = array(new MyWriter, 'write2'); // or $callable2 = array($callable1[0], 'write2');
$sfEventDispatcher->connect('log.write', $callable1);
$sfEventDispatcher->connect('log.write', $callable2);
And your callback class methods can be something like:
class MyWriter
{
public function write($event, $num)
{
// do something
}
public function write1($event)
{
$this->write($event, 1);
}
public function write2($event)
{
$this->write($event, 2);
}
}
Alternatively, you can create properties that act as state that your write function can check:
class MyWriter
{
public $state = 1;
public function write($event)
{
if ($this->state == 1) {
// do this
} else {
// do this instead
}
}
}
This is trickier as you'd have to set state before a pertinent event is triggered which may not prove feasable, depending on the specifics of your situation:
$callable[0]->state = 2;
Related
i create my own container, i made"bind" method that get "string $alias" and "Closure $closure". the method bind the alias to the object on the $container array , like that:
public function bind(string $alias,$closure)
{
$this->container[$alias] = $closure();
}
The second method is "call", that simply call to some instance from the $container. Of course first the method check if the given alias exists, and if doesnt throw an exception
public function call(string $alias)
{
if(array_key_exists($alias,$this->container))
return $this->container[$alias];
throw new \Exception();
}
is that good ? what more i need to add ? cause i saw on laravel for example that the container is full of method and props, and i dont know why. its just bind and call, isnt ?
My suggestion is to do the following:
Bind
public function bind(string $alias,$closure)
{
$this->container[$alias] = $closure;
}
Call (new instance per call)
public function call(string $alias)
{
if(array_key_exists($alias,$this->container)) {
$closure = $this->container[$alias];
return $closure();
}
throw new \Exception();
}
Call (singleton behaviour)
public function call(string $alias)
{
if (array_key_exists($alias,$this->instanceContainer)) {
return $this->instanceContainer[$alias];
}
if(array_key_exists($alias,$this->container)) {
$closure = $this->container[$alias];
$this->instanceContainer[$alias] = $closure();
return $this->instanceContainer[$alias];
}
throw new \Exception();
}
Which one of the two implementations you pick for the call will depend on your needs. If you need to call the function only once per object go for the second one. You could also have 2 methods: bind and bindSingleton to differentiate the two different binding types.
There are two main reasons I recommend this:
The object is generated on demand, which means that if you bind 1000 objects but only use 10 you'll only create 10 objects.
The result of the $closure() method may be different based on the time it was called. For example, if you want to instantiate an object which contains a time-stamp, instantiating it on call rather than on bind is more reasonable.
I have the following piece of code:
$evManager = $di->getShared('eventsManager');
$evManager->attach('dispatch', function($event, $dispatcher, $exception){
$dispatcher = new \Phalcon\Mvc\Dispatcher();
$dispatcher->setEventsManager($evManager);
return $dispatcher;
})
$evManager is object that has a method called attach which takes two arguments and it's clear for me. The second parameter is an anonymous function which has three arguments ($event, $dispatcher, $exception).
So my question is what are these three parameters? Why they aren't empty? What pass they to the anonymous function? I can't understand it...
I know that the anonymous function returns dispatcher object and the method attach do something on it. The only question is about parameters.
Think of that anonymous function as being an ordinary object with a method on it. You could write that code like this:
class MyDispatcherHelper {
public function handle($event, $dispatcher, $exception) {
$dispatcher = new \Phalcon\Mvc\Dispatcher();
$dispatcher->setEventsManager($evManager);
return $dispatcher;
}
}
$evManager = $di->getShared('eventsManager');
$evManager->attach('dispatch', new MyDispatcherHelper());
So now there's no more anonymous function.
The "magic" happens inside $evManager->attach. It's definition looks something like this:
class EventsManager {
public function attach($eventName, $handler) {
// somehow listen for events named $eventName
...
// and get an instance of the Event
$myEvent = $listener->theEvent;
// if it's an exception maybe set $exception to something usefull?
...
//_call_ $handler when event occurs
call_user_func($handler, [$myEvent, $this, $exception]);
}
}
You should read the docs for call_user_func.
Now if we continue with my "replace anonymous function with class example" the above code would look like this:
class EventsManager {
public function attach($eventName, MyDispatcherHelper $handler) {
// somehow listen for events named $eventName
...
// and get an instance of the Event
$myEvent = $listener->theEvent;
// if it's an exception maybe set $exception to something usefull?
...
//_call_ $handler when event occurs
$handler->handle($myEvent, $this, $exception);
}
}
That's what an anonymous function does.
Your code has nothing to do with calling that function. It is not under your control, you cannot tell it what parameters to call the anonymous function with, that's what eventsManager does.
An anonymous function is not called where you define it, and you can define any number of parameters on it and name them whatever you like.
Also the code inside the anonymous function might look like it does some magic regarding the code outside of it but it does not. $dispatcher->setEventsManager($evManager) is also wrong, I'm not seeing a global $evManager anywhere.
Those parameters usually tend to provide some additional information when working with plugin-like architecture. For example, if you have a Dependency Injection container, like
$di->register('translator', function($di){
// You can omit usage of $di here, because you don't need to grab from the container at right now
return new Translator();
});
$di->register('Message', function($di){
$translator = $di->get('translator');
return new Message($translator);
});
Then in some cases you might need to grab a dependency, while in some cases you don't.
How it works?
That's simple.
You simply assume that a parameter will be a function and therefore you pass arguments to it right at declaration. For example, in that $di class definition, it would look like this:
class Di
{
public function register($name, $provider)
{
// We will assume that $provider is a function
// and therefore pass some data to it
$this->data[$name] => $provider($this); // or another parameter (s)
}
}
I need an idea to create anonymous class on PHP. I don't know how I can works.
See my limitations:
On PHP you can't make anonymous class, like anonymous function (like class {});
On PHP you don't have class scope (except in namespaces, but it have the same problem below);
On PHP you can't use variables to specify the class name (like class $name {});
I don't have access to install the runkit PECL.
What I need, and why:
Well, I need create a function called ie create_class() that receives a key name and a anonymous class. It'll be useful for me because I want use different name class symbols that PHP can't accept. For instance:
<?php
create_class('it.is.an.example', function() {
return class { ... }
});
$obj = create_object('it.is.an.example');
?>
So, I need an idea that accept this use. I need it because on my framework I have this path: /modules/site/_login/models/path/to/model.php. So, the model.php need to declare a new class called site.login/path.to.model.
On call create_object() if the internal cache have a $class definition (like it.is.an.example it simply return the new class object. If not, need load. So I will use the $class content to search fastly what is the class file.
In PHP 7.0 there will be anonymous classes. I don't fully understand your question, but your create_class() function might look like this:
function create_class(string $key, array &$repository) {
$obj = new class($key) {
private $key;
function __construct($key) {
$this->key = $key;
}
};
$repository[$key] = $obj;
return $obj;
}
This will instantiate an object with an anonymous class type and register it into the $repository. To get an object out you use the key you created it with: $repository['it.is.an.example'].
You can create a dummy class using stdClass
$the_obj = new stdClass();
So basically you want to implement a factory pattern.
Class Factory() {
static $cache = array();
public static getClass($class, Array $params = null) {
// Need to include the inc or php file in order to create the class
if (array_key_exists($class, self::$cache) {
throw new Exception("Class already exists");
}
self::$cache[$class] = $class;
return new $class($params);
}
}
public youClass1() {
public __construct(Array $params = null) {
...
}
}
Add a cache within to check for duplicates
If you really need to to that, you could use eval()
$code = "class {$className} { ... }";
eval($code);
$obj = new $className ();
But the gods won't approve this. You will go to hell if you do it.
I'm writing an API class, and my general goal is for it to be easy to make any class's methods accessible via the API, without having to make any serious changes to the class itself. Essentially, I should be able to instantiate an API class instance on any class that I want to use (within my little framework), and have it just work.
For example, In my API class, I have a method call, that I want to use $_GET to call the correct function from the class that I want to make accessible (let's call it Beep). So I specify an action parameter in my API, so that the action is the method of Beep to call, with the remaining arguments in $_GET being, presumably, the arguments for the method. In API->call, I can do $BeepInstance->$_GET['action'](), but I have no way of determining which arguments from $_GET to send, and in what order to send them.
func_get_args will only return the list of given arguments for the function in which it is called, and I don't necessarily know the correct order in which to pass them with call_user_func_array.
Has anyone tried to do something similar to this?
Here's a solution + example that uses reflection to map your input arguments to method parameters. I also added a way to control which methods are exposed to make it more secure.
class Dispatch {
private $apis;
public function registerAPI($api, $name, $exposedActions) {
$this->apis[$name] = array(
'api' => $api,
'exposedActions' => $exposedActions
);
}
public function handleRequest($apiName, $action, $arguments) {
if (isset($this->apis[$apiName])) {
$api = $this->apis[$apiName]['api'];
// check that the action is exposed
if (in_array($action, $this->apis[$apiName]['exposedActions'])) {
// execute action
// get method reflection & parameters
$reflection = new ReflectionClass($api);
$method = $reflection->getMethod($action);
// map $arguments to $orderedArguments for the function
$orderedArguments = array();
foreach ($method->getParameters() as $parameter) {
if (array_key_exists($parameter->name, $arguments)) {
$orderedArguments[] = $arguments[$parameter->name];
} else if ($parameter->isOptional()) {
$orderedArguments[] = $parameter->getDefaultValue();
} else {
throw new InvalidArgumentException("Parameter {$parameter->name} is required");
}
}
// call method with ordered arguments
return call_user_func_array(array($api, $action), $orderedArguments);
} else {
throw new InvalidArgumentException("Action {$action} is not exposed");
}
} else {
throw new InvalidArgumentException("API {$apiName} is not registered");
}
}
}
class Beep {
public function doBeep($tone = 15000)
{
echo 'beep at ' . $tone;
}
public function notExposedInAPI()
{
// do secret stuff
}
}
Example:
// dispatch.php?api=beep&action=doBeep&tone=20000
$beep = new Beep();
$dispatch = new Dispatch();
$dispatch->registerAPI($beep, 'beep', array('doBeep'));
$dispatch->handleRequest($_GET['api'], $_GET['action'], $_GET);
We did something similar in our API. We used a proxy method _methodName($p) and passed in the $_GET or $_REQUEST array. The proxy method knows the order of the parameters required for the real method, so it invokes the real method correctly. Using call_user_func_array() worked pretty well with that.
Not sure if that's the best way to go about it, but it works well for us.
The controller looks something like this:
if (method_exists($server, "_$method"))
$resp = call_user_func_array("{$server}::_$method", array($_REQUEST));
And then the model is setup like:
public function test($arg1, $arg2) { ... }
public function _test($p) {
return $this->test($p['arg1'], $p['arg2']);
}
I'd propose to pass an associative array the the respective method. Since the assoc. array provides a name to value mapping.
Moreover, never do something like this:
$BeepInstance->$_GET['action']()
This is highly insecure.
Probably define another associate array, which maps actions passed as GET 'action' parameters to actual method names.
I want to add custom event handler to object's method.
I've got a class with method.
class Post {
public function Add($title) {
// beforeAdd event should be called here
echo 'Post "' . $title . '" added.';
return;
}
}
I want to add an event to method Add and pass method's argument(s) to the event handler.
function AddEventHandler($event, $handler){
// What should this function do?
}
$handler = function($title){
return strtoupper($title);
}
AddEventHandler('beforeAdd', $handler);
Is it possible to do something like this? Hope my question is clear.
Should be pretty easy using the functions defined here http://www.php.net/manual/en/book.funchand.php
In particular you should keep an handler array (or array of arrays if you want multiple handlers for the same event) and then just do something like
function AddEventHandler($event, $handler){
$handlerArray[$event] = $handler;
}
or
function AddEventHandler($event, $handler){
$handlerArray[$event][] = $handler;
}
in case of multiple handlers.
Invoking the handlers then would be just matter of calling "call_user_func" (eventually in a cycle if multiple handlers are needed)
Well, if you are using < php 5.3 then you cannot create a closure in such a way, but you can come close with create_function(); This would be
$handler = create_function('$title', 'return strtoupper($title);');
Then you store $handler in the class and you can call it as you desire.
Methods
You have multiple methods how to do it described by ircmaxell here.
And here is ToroHook used in ToroPHP (Routing lib).
Hook
class ToroHook {
private static $instance;
private $hooks = array();
private function __construct() {}
private function __clone() {}
public static function add($hook_name, $fn){
$instance = self::get_instance();
$instance->hooks[$hook_name][] = $fn;
}
public static function fire($hook_name, $params = null){
$instance = self::get_instance();
if (isset($instance->hooks[$hook_name])) {
foreach ($instance->hooks[$hook_name] as $fn) {
call_user_func_array($fn, array(&$params));
}
}
}
public static function remove($hook_name){
$instance = self::get_instance();
unset($instance->hooks[$hook_name]);
var_dump($instance->hooks);
}
public static function get_instance(){
if (empty(self::$instance)) {
self::$instance = new Hook();
}
return self::$instance;
}
}
Using hook
It is simple call it like this:
ToroHook::add('404', function($errorpage){
render("page/not_found", array("errorpage" => $errorpage));
});
Take look on my sphido/events library:
it's easy to use (few lines of code)
based on PHP Function handling
allow prioritising listeners
add/remove listeners
filter values by functions
stop propagation in function chain
add default handler
Event handler example
on('event', function () {
echo "wow it's works yeah!";
});
fire('event'); // print wow it's works yeah!
Filter function example
add_filter('price', function($price) {
return (int)$price . ' USD';
});
echo filter('price', 100); // print 100 USD