I'm trying to extend my ActiveRecord class with some dynamic methods. I would like to be able to run this from my controller
$user = User::find_by_username(param);
$user = User::find_by_email(param);
I've read a little about overloading and think that's the key. I'v got a static $_attributes in my AR class and I get the table name by pluralizing my model (User = users) in this case.
How do I do this? All models extends the ActiveRecord class.
You have to use the __callStatic() magic method, which is available as PHP5.3
public static function __callStatic($name, $arguments) {
/*
Use strpos to see if $name begins with 'find_by'
If so, use strstr to get everything after 'find_by_'
call_user_func_array to regular find method with found part and $arguments
return result
*/
}
This might also be of use, it's more complex, but it allows true dynamic functions with access to member variables.
class DynamicFunction {
var $functionPointer;
var $mv = "The Member Variable";
function __construct() {
$this->functionPointer = function($arg) {
return sprintf("I am the default closure, argument is %s\n", $arg);
};
}
function changeFunction($functionSource) {
$functionSource = str_replace('$this', '$_this', $functionSource);
$_this = clone $this;
$f = '$this->functionPointer = function($arg) use ($_this) {' . PHP_EOL;
$f.= $functionSource . PHP_EOL . "};";
eval($f);
}
function __call($method, $args) {
if ( $this->{$method} instanceof Closure ) {
return call_user_func_array($this->{$method},$args);
} else {
throw new Exception("Invalid Function");
}
}
}
if (!empty($argc) && !strcmp(basename($argv[0]), basename(__FILE__))) {
$dfstring1 = 'return sprintf("I am dynamic function 1, argument is %s, member variables is %s\n", $arg, $this->mv);';
$dfstring2 = 'return sprintf("I am dynamic function 2, argument is %s, member variables is %s\n", $arg, $this->mv);';
$df = new DynamicFunction();
$df->changeFunction($dfstring1);
echo $df->functionPointer("Rabbit");
$df->changeFunction($dfstring2);
$df->mv = "A different var";
echo $df->functionPointer("Cow");
};
Related
I understand that one can use interfaces to mandate the definition of a function, but I cannot find something that enables one to mandate function calls, such that e.g. if I create a class being a member of another class (via extends, etc), with a function, for that class to automatically ensure that mandatory functions are called in part with that function.
I mean, to clarify further:
class domain {
function isEmpty($input) {
//apply conditional logic and results
}
}
class test extends domain {
function addTestToDBTable($test) {
/**
* try to add but this class automatically makes it so that all rules of
* class domain must be passed before it can run
* - so essentially, I am no longer required to call those tests for each and
* every method
**/
}
}
Apologies if this appears incoherent by any means. Sure, it seems lazy but I want to be able to force context without having to concern abou
Update:
Okay, to clarify further: in PHP, if I extend and declare a __construct() for a child class, that child class will override the parent __construct(). I do not want this, I want the parent construct to remain and mandate whatever as it pleases just as the child class may do so also.
I guess it can be done in two different ways.
Aspect Oriented Programming
Have a look here https://github.com/AOP-PHP/AOP
Generate or write Proxy classes
A really simple example could be:
<?php
class A {
public function callMe() {
echo __METHOD__ . "\n";
}
}
class B extends A {
// prevents instantiation
public function __construct() {
}
public function shouldCallMe() {
echo __METHOD__ . "\n";
}
public static function newInstance() {
return new ABProxy();
}
}
class ABProxy {
private $b;
public function __construct() {
$this->b = new B();
}
public function __call($method, $args) {
$this->b->callMe();
return call_user_func_array(array($this->b, $method), $args);
}
}
// make the call
$b = B::newInstance();
$b->shouldCallMe();
// Outputs
// ------------------
// A::callMe
// B::shouldCallMe
Hopes this helps a bit.
Sounds like you want a Decorator.
See This answer for a detailed explanation on how to do it. Note that it does not require a class extension.
I would use a domain-validating decorator with some doc-block metaprogramming magic. But this is really a job for an entire library, which no doubt exists.
fiddle
<?php
class FooDomain {
public static function is_not_empty($input) {
return !empty($input);
}
}
class Foo {
/**
* #domain FooDomain::is_not_empty my_string
*/
public function print_string($my_string) {
echo $my_string . PHP_EOL;
}
}
$foo = new DomainValidator(new Foo());
$foo->print_string('Hello, world!');
try {
$foo->print_string(''); // throws a DomainException
} catch (\DomainException $e) {
echo 'Could not print an empty string...' . PHP_EOL;
}
// ---
class DomainValidator {
const DOMAIN_TAG = '#domain';
private $object;
public function __construct($object) {
$this->object = $object;
}
public function __call($function, $arguments) {
if (!$this->verify_domain($function, $arguments)) {
throw new \DomainException('Bad domain!');
}
return call_user_func_array(
array($this->object, $function),
$arguments
);
}
public function __get($name) {
return $this->object->name;
}
public function __set($name, $value) {
$this->object->name = $value;
}
private function verify_domain($function, $arguments) {
// Get reference to method
$method = new \ReflectionMethod($this->object, $function);
$domains = $this->get_domains($method->getDocComment());
$arguments = $this->parse_arguments(
$method->getParameters(),
$arguments
);
foreach ($domains as $domain) {
if (!call_user_func(
$domain['name'],
$arguments[$domain['parameter']]
)) {
return false;
}
}
return true;
}
private function get_domains($doc_block) {
$lines = explode("\n", $doc_block);
$domains = array();
$domain_tag = DomainValidator::DOMAIN_TAG . ' ';
foreach ($lines as $line) {
$has_domain = stristr($line, $domain_tag) !== false;
if ($has_domain) {
$domain_info = explode($domain_tag, $line);
$domain_info = explode(' ', $domain_info[1]);
$domains[] = array(
'name' => $domain_info[0],
'parameter' => $domain_info[1],
);
}
}
return $domains;
}
private function parse_arguments($parameters, $values) {
$ret = array();
for ($i = 0, $size = sizeof($values); $i < $size; $i++) {
$ret[$parameters[$i]->name] = $values[$i];
}
return $ret;
}
}
Output:
Hello, world!
Could not print an empty string...
I am wondering if there is a way to attach a new method to a class at runtime, in php.
I mean, not on an instance level but directly to the class, so that all newly created instances, have this new method.
Can such a thing be done with reflection?
Thanks
Yes, you can.
Below is the way to create method in runtime in php 5.4.x.
The anonymous function is represented by Closure class started from 5.3.x. From 5.4.x, it add a Closure::bind static method to bind the anonymous function to a particular object or class.
Example:
class Foo {
private $methods = array();
public function addBar() {
$barFunc = function () {
var_dump($this->methods);
};
$this->methods['bar'] = \Closure::bind($barFunc, $this, get_class());
}
function __call($method, $args) {
if(is_callable($this->methods[$method]))
{
return call_user_func_array($this->methods[$method], $args);
}
}
}
$foo = new Foo;
$foo->addBar();
$foo->bar();
Did some playing around with whole thing. Seems that only thing you can potentially do with ReflectionClass is to replace an existing method. But even that would be indirectly.
I actually do not know any class-based language, where dynamic classes exist (then again, my knowledge is quite limited). I have seen it done only in prototype-based languages (javascript, ruby, smalltalk). Instead what you can do, in PHP 5.4, is to use Closure and add new methods to an existing object.
Here is a class which would let you perform such perversion to any object:
class Container
{
protected $target;
protected $className;
protected $methods = [];
public function __construct( $target )
{
$this->target = $target;
}
public function attach( $name, $method )
{
if ( !$this->className )
{
$this->className = get_class( $this->target );
}
$binded = Closure::bind( $method, $this->target, $this->className );
$this->methods[$name] = $binded;
}
public function __call( $name, $arguments )
{
if ( array_key_exists( $name, $this->methods ) )
{
return call_user_func_array( $this->methods[$name] , $arguments );
}
if ( method_exists( $this->target, $name ) )
{
return call_user_func_array(
array( $this->target, $name ),
$arguments
);
}
}
}
To use this, you have to provide constructor with an existing object. Here is small example of usage:
class Foo
{
private $bar = 'payload';
};
$foobar = new Foo;
// you initial object
$instance = new Container( $foobar );
$func = function ( $param )
{
return 'Get ' . $this->bar . ' and ' . $param;
};
$instance->attach('test', $func);
// setting up the whole thing
echo $instance->test('lorem ipsum');
// 'Get payload and lorem ipsum'
Not exactly what you want, but AFAIK this is as close you can get.
Have you taken a look at create_function() in the docs? You might also achieve the desired result by overloading.
This is possible with the runkit extension's runkit_method_add(). Be careful using this in production though.
Example:
<?php
class Example {}
$e = new Example();
runkit_method_add(
'Example',
'add',
'$num1, $num2',
'return $num1 + $num2;',
RUNKIT_ACC_PUBLIC
);
echo $e->add(12, 4);
You can use one of the below two methods also.
function method1()
{
echo "In method one.";
}
function method2()
{
echo "In method two.";
}
class DynamicClass
{
function __construct(){
$function_names = ['method1'];
foreach ($function_names as $function_name) {
if (function_exists($function_name)) {
$this->addMethod($function_name);
}
}
}
function addMethod($name)
{
$this->{$name} = Closure::fromCallable($name);
}
public function __call($name, $arguments)
{
return call_user_func($this->{$name}, $arguments);
}
}
$obj = new DynamicClass();
//Call method1 added in constructor
$obj->method1();
//Add method
$obj->addMethod('method2');
$obj->method2();
This is not really a problem but more of a question.
My question is how it's 'supposed to be' programmed.
It might not be too clear to explain, though.
So my question is; do I have to make multiple methods to retrieve data from a database?
For example, I have a table called FRUITS.
It contains the ID, name and date the fruit was added.
Now I want to get the name of the fruit based on a given ID, and later on in the script I want to get the date of the fruit as well.
Should I make one method such as get_fruit($id) which returns both the name and date, or two separate methods get_name($id) and get_date($id)?
Thanks in advance.
You should use one object which would contain all the required data. For example:
class Fruit {
protected ... variables;
public function getId() {...}
public function getDate() {...}
...
}
Also implementing __set and __get would be nice example of using full php potential.
You also may implement save() method (or extend database row class, such as Zend_Db_Table_Row.
So whole code would look like:
$fruit = $model->getFruid( 7); // $id = 7 :)
echo $fruit->id; // would call internally $fruit->__get( 'id')
echo $fruit->date;
// And modification:
$fruit->data = '2011-08-07';
$fruit->save();
EDIT: using separate methods to load certain data is useful (only?) when you need to load large amount of data (such as long texts) which is required only on one place in your code and would affect performance.
EDIT 2: (answer to comment):
__get and __set are called when you try to access undefined property of an object, for example:
class Foo {
public $bar;
public function __get( $name){
echo $name "\n";
return 'This value was loaded';
}
}
// Try to use
Foo $foo;
echo $foo->bar . "\n";
echo $foo->foo . "\n";
There are two "large" approaches to this that I know about:
// First use __get and __set to access internal array containing data
class DbObject {
protected $data = array();
public function __get( $propertyName){
// Cannot use isset because of null values
if( !array_key_exits( $propertyName,$this->data)){
throw new Exception( 'Undefined key....');
}
return $this->data[ $propertyName];
}
// Don't forget to implement __set, __isset
}
// Second try to call getters and setter, such as:
class DbObject {
public function getId() {
return $this->id;
}
public function __get( $propertyName){
$methodName = 'get' . ucfirst( $propertyName);
if( !method_exits( array( $this, $methodName)){
throw new Exception( 'Undefined key....');
}
return $this->$methodName();
}
}
To sum up... First approach is easy to implement, is fully automatized... You don't need large amount of code and sources would be pretty much the same for every class. The second approach required more coding, but gives you a better control. For example:
public function setDate( $date){
$this->date = date( 'Y-m-d h:i:s', strtotime( $date));
}
But on the other hand, you can do this with first approach:
class Fruit extends DbObject {
public function __set( $key, $val){
switch( $key){
case 'date':
return $this->setDate( $val);
default:
return parent::__set( $key, $val);
}
}
}
Or you can use total combination and check for getter/setter first and than try to access property directly...
here is the code how you can use the one function to get different field's value.
function get_fruit($id,$field = ''){
$sql = "select * from table_name where id = $id";
$result = mysql_fetch_object(mysql_query($sql));
if($field != ''){
return $result->$field;
}else{
return $result;
}
}
echo get_fruit(1,'field_name');
class data_retrieve
{
public $tablename;
public $dbname;
public $fieldset;
public $data_array;
public $num_rows
function __construct()
{
$this->tablename='junk';
$this->dbname='test';
$this->fieldset=array('junk_id');
}
function getData($where_str)
{
$this->data_array= array();
global $dbconnect, $query;
if($dbconnect ==0 )
{
echo" Already Connected \n ";
$dbconnect=db_connect("objectdb") or die("cannot connect");
}
$where_str;
if(empty($where_str))
{
$where_str=NULL;
}
else
{
$where_str= "where". $where_str ;
}
$query= "select * from $this->tablename $where_str";
$record= mysql_query($query) or die($query);
$recNo=mysql_num_rows($record);
$this->num_rows=$recNo;
while($row= mysql_fetch_assoc($record))
{
$this->data_array[]=$row;
}
mysql_free_result($record);
return $this->data_array;
}
class fruit extends data_retrieve
{
function __construct()
{
parent::__construct();
$this->tablename='fruit';
$this->fieldset=array('fruit_id','fruit_name','date');
}
}
then
in your file create a fruit object like
$str="fruit_id=5";
$fruit_data = new fruit();
$records=$fruit_data->getData($str);
to display
foreach($records as $row )
{
print <<< HERE
<label class='table_content' > $row[fruit_id]</label>
<label class='table_content' > $row[fruit_name]</label>
<label class='table_content' > $row[date]</label>
HERE;
}
I'm trying to add methods dynamically from external files.
Right now I have __call method in my class so when i call the method I want, __call includes it for me; the problem is I want to call loaded function by using my class, and I don't want loaded function outside of the class;
Class myClass
{
function__call($name, $args)
{
require_once($name.".php");
}
}
echoA.php:
function echoA()
{
echo("A");
}
then i want to use it like:
$myClass = new myClass();
$myClass->echoA();
Any advice will be appreciated.
Is this what you need?
$methodOne = function ()
{
echo "I am doing one.".PHP_EOL;
};
$methodTwo = function ()
{
echo "I am doing two.".PHP_EOL;
};
class Composite
{
function addMethod($name, $method)
{
$this->{$name} = $method;
}
public function __call($name, $arguments)
{
return call_user_func($this->{$name}, $arguments);
}
}
$one = new Composite();
$one -> addMethod("method1", $methodOne);
$one -> method1();
$one -> addMethod("method2", $methodTwo);
$one -> method2();
You cannot dynamically add methods to a class at runtime, period.*
PHP simply isn't a very duck-punchable language.
* Without ugly hacks.
You can dynamically add attributes and methods providing it is done through the constructor in the same way you can pass a function as argument of another function.
class Example {
function __construct($f)
{
$this->action=$f;
}
}
function fun() {
echo "hello\n";
}
$ex1 = new class('fun');
You can not call directlry $ex1->action(), it must be assigned to a variable and then you can call this variable like a function.
if i read the manual right,
the __call get called insted of the function, if the function dosn't exist
so you probely need to call it after you created it
Class myClass
{
function __call($name, $args)
{
require_once($name.".php");
$this->$name($args);
}
}
You can create an attribute in your class : methods=[]
and use create_function for create lambda function.
Stock it in the methods attribute, at index of the name of method you want.
use :
function __call($method, $arguments)
{
if(method_exists($this, $method))
$this->$method($arguments);
else
$this->methods[$method]($arguments);
}
to find and call good method.
What you are referring to is called Overloading. Read all about it in the PHP Manual
/**
* #method Talk hello(string $name)
* #method Talk goodbye(string $name)
*/
class Talk {
private $methods = [];
public function __construct(array $methods) {
$this->methods = $methods;
}
public function __call(string $method, array $arguments): Talk {
if ($func = $this->methods[$method] ?? false) {
$func(...$arguments);
return $this;
}
throw new \RuntimeException(sprintf('Missing %s method.'));
}
}
$howdy = new Talk([
'hello' => function(string $name) {
echo sprintf('Hello %s!%s', $name, PHP_EOL);
},
'goodbye' => function(string $name) {
echo sprintf('Goodbye %s!%s', $name, PHP_EOL);
},
]);
$howdy
->hello('Jim')
->goodbye('Joe');
https://3v4l.org/iIhph
You can do both adding methods and properties dynamically.
Properties:
class XXX
{
public function __construct($array1)
{
foreach ($array1 as $item) {
$this->$item = "PropValue for property : " . $item;
}
}
}
$a1 = array("prop1", "prop2", "prop3", "prop4");
$class1 = new XXX($a1);
echo $class1->prop1 . PHP_EOL;
echo $class1->prop2 . PHP_EOL;
echo $class1->prop3 . PHP_EOL;
echo $class1->prop4 . PHP_EOL;
Methods:
//using anounymous function
$method1 = function () {
echo "this can be in an include file and read inline." . PHP_EOL;
};
class class1
{
//build the new method from the constructor, not required to do it here by it is simpler.
public function __construct($functionName, $body)
{
$this->{$functionName} = $body;
}
public function __call($functionName, $arguments)
{
return call_user_func($this->{$functionName}, $arguments);
}
}
//pass the new method name and the refernce to the anounymous function
$myObjectWithNewMethod = new class1("method1", $method1);
$myObjectWithNewMethod->method1();
I've worked up the following code example and a helper method which works with __call which may prove useful. https://github.com/permanenttourist/helpers/tree/master/PHP/php_append_methods
How can i perform a function once a variable's value has been set?
say like
$obj = new object(); // dont perform $obj->my_function() just yet
$obj->my_var = 67 // $obj->my_function() now gets run
I want the object to do this function and now having to be called by the script.
Thanks
EDIT
my_var is predefined in the class, __set is not working for me.
Use a private property so __set() is invoked:
class Myclass {
private $my_var;
private $my_var_set = false;
public function __set($var, $value) {
if ($var == 'my_var' && !$this->my_var_set) {
// call some function
$this->my_var_set = true;
}
$this->$var = $value;
}
public function __get($var, $value) {
return $this->$name;
}
}
See Overloading. __set() is called because $my_var is inaccessible and there is your hook.
I'd recommend to create a setter function for $obj and include the relevant function call there. So basically your code would look somehow like this:
$obj = new ClassOfYours();
$obj->setThatValue("apple");
Of course you would have to take care that all assignments to ThatValue need to be
done through that setter in order make it work properly. Assuming that you're on php5 I'd set that property to private, so all direct assignments will cause an runtime error.
A good overview about OOP in php can be found in this article on devarticles.com.
HTH
To acheive exactly what you describe, you'd have to use a magic setter.
class ObjectWithSetter {
var $data = array();
public function my_function() {
echo "FOO";
}
public function __set($name, $value) {
$this->data[$name] = $value;
if($name == 'my_var') {
$this->my_function();
}
}
public function __get($name) {
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
$trace = debug_backtrace();
trigger_error(
'Undefined property via __get(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
/** As of PHP 5.1.0 */
public function __isset($name) {
return isset($this->data[$name]);
}
public function __unset($name) {
unset($this->data[$name]);
}
}
Assuming you want to call my_function() once you set a value, that case you can encapsulate both the operations into one. Something like you create a new function set_my_var(value)
function set_my_var(varvalue)
{
$this->my_var = varvalue;
$this->my_function();
}