Passing objects/references as function parameters - php

EDIT: Language is PHP.
So, I'm implementing a listener/observer pattern. One of the objects I implemented was an object for including files. Which works in a way like this:
class IncludeEvent extends Event {
protected $File = '';
public $Contents;
....
public function Begin() {
....
// this is the ONLY time $Contents is modified:
$this->Contents = ob_get_contents();
ob_end_clean();
....
}
....
if ($this->Notify(new IncludeObject($this->File,'OBOutput', $this)) !== false) {
print $this->Contents;
}
}
Now, whenever an object wants to signal its listeners that an event happened, they need to use the Notify function.
So at one point I had to send a reference to the object to its listeners, so they could have access to the included content ($Contents).
So I used Notify() passing $this as the parameter. When I try to access the $Content from the other object, it simply returns as an empty string.
Note: The object functions as it should in a normal context, only when I pass it as a function parameter I can't access its $Content.
The constructor for IncludeObject():
public function __construct($F= '', $M= -1, &$IV = null) {
$this->File = $F;
$this->Func = $M;
$this->IncEv = $IV;
}
The listener accesses the IncludeEvent object like this:
public function OBOutput($IncObj){
print $IncObj->IncEv->Contents;
return false;
}
So yeah, how do I access long strings from object references? It works fine with short strings, but something like a long included file just returns empty.
Notify() is defined as:
final public function Notify($EvObj = null) {
foreach ($this->listen_list as $z) {
$d = call_user_func(array($z, $EvObj->Func), $EvObj);
if ($d == true || is_null($d)) {
return true;
}
}
return false;
}

how do I access long strings from object references? It works fine with short strings, but something like a long included file just returns empty.
you're barking up the wrong tree. and btw, you don't need that & at all. objects are passed by reference without it (not in PHP 4, but you shouldn't be using that nowadays).

Related

Check if callable can receive class as parameter in php

I have a callable $f and I would like to know if it can receive an instance of a certain class Foo as input.
At the moment I'm doing something like
try {
$f($foo);
} catch (\TypeError $e) {
throw new \InvalidArgumentException('The provided function can not evaluate inputs of this type');
}
Is there a way to check this WITHOUT actually invoking the callable? Maybe with reflection or some other dark magic?
If you want to be able to reflect any kind of callable, you'll need to wrap up the logic in a small function. Depending on whether you've got an array, a function name or an anonymous function, you need to create either a ReflectionFunction or ReflectionMethod. Fortunately, these both extend ReflectionFunctionAbstract, so we can type-hint the return value.
function reflectCallable($arg): ReflectionFunctionAbstract {
if (is_array($arg)) {
$ref = new ReflectionMethod(...$arg);
} elseif (is_callable($arg)) {
$ref = new ReflectionFunction($arg);
}
return $ref;
}
This will return you the appropriate object for your callable value, which you can then use to fetch the parameters and act accordingly:
function definedFunc(Foo $foo) {}
$callable = function(Foo $foo) {};
class Bar { public function baz(Foo $foo) {} }
foreach (['definedFunc', $callable, ['Bar', 'baz']] as $callable) {
$reflected = reflectCallable($callable);
if ((string) $reflected->getParameters()[0]->getType() === 'Foo') {
echo 'Callable takes Foo', PHP_EOL;
}
}
See https://3v4l.org/c5vmM
Note that this doesn't do any error handling - you'll probably get warnings/notices if the callable doesn't take any parameters or the first parameter doesn't have a type. It also requires PHP 7+, but hopefully that's not an issue.
It doesn't currently support objects that implement __invoke or static calls defined as "Foo::bar", but they wouldn't be too hard to add if necessary. I've just found something very similar in the source of Twig, which does a more thorough job: https://github.com/twigphp/Twig/blob/v2.8.0/src/Node/Expression/CallExpression.php#L280
You can with ReflectionParameter::getType:
$f = function(Foo $foo) {};
$reflectionFunc = new ReflectionFunction($f);
$reflectionParams = $reflectionFunc->getParameters();
$reflectionType1 = $reflectionParams[0]->getType();
echo $reflectionType1;
output:
Foo

How to Convert a Function into a Closure in PHP?

I have function that uploads files to server:
function upload() {
$files = Input::file(self::$filename);
foreach ($files as $key => $file) {
// Validation
// Uploading
}
}
So, how to make this function as closure? That after loading it returns error or success information to output variable:
$results = function upload() {
}
In results should be result array of uploading files. It can be error or success state.
Since you're using self:: I assume you expect to run this function from inside a class. But you can't really do this - the function should be defined in the class to be able to use self::.
But really, you can just pass it as a parameter.
function upload($filename) {
$files = Input::file($filename); // note, self:: removed
foreach ($files as $key => $file) {
// Validation
// Uploading
}
return $result;
}
And when you need to use it inside your class, call it this way:
$result = upload(self::$filename);
For Converting a Function into a Closure in PHP, you can :
Since PHP 7 >= 7.1.0, you can use Closure::fromCallable()
$cl = Closure::fromCallable("upload");
$result = $cl();
Before (PHP >= 5.4.0), you can use ReflectionFunction::getClosure()
$reflexion = new ReflectionFunction('upload');
$cl = $reflexion->getClosure();
$result = $cl();
Another idea is to construct a new function that call your main function :
$cl = function (...$args) {
return upload(...$args);
};
Updated and Expanded
This answer addresses the OP's primary question of "How to Convert a Function into a Closure in PHP?" One way involves writing a nameless function and then assigning it to a variable. Since the OP's upload code is problematic, i.e. a method without a class, I therefore borrow the corrected code of #astax, modifying it for the purpose of illustrating the mechanics of how to create a closure in PHP, as follows:
<?php
$f = function( $filename ) {
$files = Input::file( $filename );
foreach ($files as $key => $file) {
// Validation
// Uploading
}
return $arrResults;
};
Note the trailing semicolon after the final brace. Also, the closure definition consists of an anonymous function. You may get the result as follows:
<?php
$arrResults = $f( $filename );
// or in a class:
$arrResults = $f( self::$filename );
Note: PHP does not have named closures, so the function needs to be anonymous. Usually it is assigned to a variable but PHP 7 provides more flexibility such that you may avoid setting a variable as the following two basic examples indicate:
<?php
echo (function() {
return 2 * 2;
})(),"\n",
(function(){
return function(){
return 3 * 3;
};
})()();
See live code.
This syntax is possible thanks to the support for function call chaining in PHP 7.
Note: if your code includes a return statement, when the closure executes, you may assign the return value to a variable.
Incidentally, two useful articles on PHP's closures: see this issue as well in this issue.
Lastly, note while other languages recognize a bound variable with a function as a closure and a nameless function as an anonymous function, in PHP support for each is done vis a vis the Closure class.
Caveat:
Care should be taken when creating a closure from the Closure object's fromCallable method since it evidently yields a more restricted closure as the following code demonstrates:
<?php
class A {
private $name;
public function __construct($name)
{
$this->name = $name;
}
}
$person = new A("Tim");
function test() {
return $this->name;
}
$f = function() {
return $this->name;
};
$cl = Closure::fromCallable( $f );
echo $cl->bindTo($person, 'A')();
echo "\n\nBut, Closure::fromCallable problematic:\n";
try {
$cl = Closure::fromCallable( "test" );
echo $cl->bindTo($person, 'A')();
} catch (Error $e) {
echo "Unable to bind closure from callable because: \n";
echo $e->getMessage();
}
see live code
The error message that results is as follows:
Warning: Cannot rebind scope of closure created by
ReflectionFunctionAbstract::getClosure() in /in/qso7b on line 26
Unable to bind closure from callable because: Function name must be a
string

php OOP return from function

some strange question, here is my code:
I have to echo $list, but now it's NULL.
the "class GuestBook" read from a file from server, then I add some new info to array and I want to return a new array (array is $list), now it display that $list is NULL
<?php
class GuestBook {
public function __construct($path)
{
$this->path = $path;
$this->list = $list;
}
public function getData() {
$list = file($this->path);
return $this->list;
}
public function append($text)
{
$list = file($this->path);
$list[] = $text;
var_dump($list);
return $this->list;
}
public function getList()
{
var_dump($list); // NULL
}
};
$newBook = new GuestBook(__DIR__ . '/HW_db.txt');
$newBook->getData();
$newBook->append('HI!');
$newBook->getList(); // NULL??
var_dump($newBook->list); // NULL ??
What is the error here??
Simply put, this will work.
<?php
class GuestBook {
public $path,$list;
public function __construct($path){
$this->path = $path;
}
public function getData() {
if(file_exists($this->path)){
$this->list = file($this->path);
return $this;
}
throw new \Exception(" Your file path is invalid ");
return null;
}
public function append($text){
$this->list[] = $text;
return $this;
}
public function getList(){
return $this->list;
}
}
//usage
$guestbook = new GuestBook(__DIR__ . '/HW_db.txt');
$guestbook->getData()->append("HI");
var_dump($guestbook->getList());
?>
How does it work
Ok, so let's start from the top; first we declare the class name, after which we set the variables. In this case you used $path and $list, for simplicity we set them to public. If you would like to know more about variable scope, I refer to the manual.
Then we move on to the constructor, we set the path we get passed to that public $path variable, so we can use it throughout the class.
The getData() function, we check if the file exists. If it exists, we load the data into the public $list variable. If it doesn't we throw an Exception.
In order to append to that list, we just add to the array of public $list.
Then in order to get the entire list, we return $this->list in the getList() function.
With all that, you have a well functioning class.
You are using local variable, instead of a class property. Correct is:
public function getList()
{
var_dump($this->list);
}
What is wrong in your code?
Simply put: everything.
How to fix it
Let's analyze each piece of it and see how to correct and improve it.
class GuestBook {
public function __construct($path)
{
$this->path = $path;
$this->list = $list;
}
The first statement of the constructor is correct, it does what the constructor should do: it initializes the class members. It would be nice for the readers of the code to also declare $path and $list on top of the class definition like this:
class GuessBook {
private $path;
private $list;
public function __construct($path)
...
The second statement of the constructor, however, initializes the list property of the object ($this->list) with the value of variable $list that is not mentioned anywhere in the function; it doesn't exist and that means it is undefined and its value is NULL.
You can either pass $list as the second argument of the constructor (public function __construct($path, $list)) or initialize $this->list with a fixed value. From the way you use the class I think the second option is the way to go and the most appropriate value for $this->list is an empty array().
public function getData()
{
$list = file($this->path);
return $this->list;
}
The first statement loads the content of a file, splits it into lines and stores the array of lines into the local variable $list. Nothing wrong here. But the second statement returns the value of the list property of the current object that, let's remember, was initialized in the constructor with NULL.
I believe your intention is to store in the list property the array of lines loaded from the file. That is achieved by changing the first line of code to:
$this->list = file($this->path);
While we are here, the name of the method doesn't clearly represent what it does. Its main purpose is to load the content of the file in $this->list. A better name for it is loadData().
public function append($text)
{
$list = file($this->path);
$list[] = $text;
var_dump($list);
return $this->list;
}
First of all, the var_dump() has no place here (or everywhere). It is a debugging function, one puts it into the code, uses the data it displays and removes it from the code as soon as the code was fixed and its presence is not needed any more. I hope you let it there in the code posted here and you remove it from the real code.
This method has the same problem as getData(). It loads the data into the local variable $list that is destroyed as soon as the function completes. However, using $this->list instead of $list fixes only half of the problem.
What happens when you call append() twice in a row (after you have fixed it)? The second call discards the content of $this->list prepared by the first call. This (partially) happens because append() does two things: loads the data and appends a new line. Let getData() load the data and change append() to only append one new line:
public function append($text)
{
$this->list[] = $text;
return $this->list;
}
public function getList()
{
var_dump($list); // NULL
}
The function name (getList()) says it returns a value. Instead it displays something. This is confusing. Again, there is no place for var_dump() here.
But the main problem of this function is that it attempts to use the local variable $list that is not initialized. It should probably read:
public function getList()
{
return $this->list;
}
};
There is no need to put a semicolon (;) after a class definition. It is not an error, though. It is just an empty statement.
$newBook = new GuestBook(__DIR__ . '/HW_db.txt');
$newBook->getData();
$newBook->append('HI!');
$newBook->getList(); // NULL??
Nothing wrong here, just unexpected. A reader that doesn't know the code of class GuestBook expects $newBook->getList() returns a value and not print anything.
var_dump($newBook->list); // NULL ??
$newBook->list is NULL because the one and only time when something was stored in it is in the constructor. And the value put in it by the constructor is the value of an uninitialized variable, i.e. NULL.
More than that, the property list is accessible from outside the class because you didn't declare it and PHP created it as public the first time when it was set. In a correct OOP code, most of the times the properties are private or protected, only (some of) the methods are public.
How to make it work
Let's fix and put everything together:
class GuestBook {
private $path;
private $list;
public function __construct($path)
{
$this->path = $path;
$this->list = array();
}
public function loadData()
{
$this->list = file($this->path);
return $this->list;
}
public function append($text)
{
$this->list[] = $text;
return $this->list;
}
public function getList()
{
return $this->list;
}
}
$newBook = new GuestBook(__DIR__ . '/HW_db.txt');
$newBook->loadData();
$newBook->append('HI!');
print_r($newBook->getList());
While this code works, it still shows several weak points. If you call loadData() again after append(), the previously appended data is discarded and the content of the file is loaded again. If you don't call loadData() at all, the content of the file is ignored.
These flaws can be easily fixed by merging the code of loadData() into the constructor and removing the method loadData() altogether. This way the class goes one step further towards a correct OOP implementation. The users of the class does not and should not care about loading or saving the data; this is an implementation detail and it's the responsibility of the class.
Also, you need a method to put the data back into the file. This can be done in the destructor of the class.
class GuestBook {
private $path;
private $list;
public function __construct($path)
{
$this->path = $path;
$this->list = file($this->path);
}
public function append($text)
{
$this->list[] = $text;
return $this->list;
}
public function getList()
{
return $this->list;
}
public function __destruct()
{
file_put_contents(this->file, implode('', $this->list));
}
}
$newBook = new GuestBook(__DIR__ . '/HW_db.txt');
$newBook->append('HI!');
print_r($newBook->getList());
unset($newBook); // This calls the destructor
The code still has a flaw: file() loads the content of the file, splits it into lines but each line ends with the end-of-line character ("\n"). When the data is saved back into the file, the lines are joined together. If, because of append(), the list contains entries that do not end with "\n" or entries that contain embedded newlines, the next time when the data is loaded from file the content of $this->list will be different.
My suggestion is to always make sure the elements of $this->list do not contain new line characters.
Change the constructor to remove the newlines and the destructor to put them back when it saves the data in the file. Also change append() to replace the newline characters with spaces.
The final code
class GuestBook {
private $path;
private $list;
public function __construct($path)
{
$this->path = $path;
$this->list = explode("\n", file_get_contents($this->path));
}
public function append($text)
{
$this->list[] = trim(str_replace("\n", ' ', $text));
return $this->list;
}
public function getList()
{
return $this->list;
}
public function __destruct()
{
file_put_contents(this->file, implode("\n", $this->list));
}
}
What this code doesn't do and it must do
The code above works fine in an ideal world where all the files exist and are readable and writable and nothing bad ever happens. Since we are living in the real world and not in a fairy tale, bad things sometimes happen and the code must be ready to handle them.
For the sake of simplicity, the code above doesn't handle the errors: file doesn't exist, it cannot be read or written. This can be easily accomplished by using the function file_exists() and by checking the values returned by file_get_contents() and file_put_contents().
Further reading
Read the entire section about PHP classes and objects. It doesn't replace a good introduction into OOP, though. Find and read one if you feel you need (after or while you read the PHP documentation).
The documentation of PHP functions used in the code: file(), file_get_contents(), file_put_contents(), trim(), str_replace(), explode(), implode().
Also read about the scope of variables in PHP.
public function append($text) {
$list = file($this - > path);
$list[] = $text;
var_dump($list);
return $this - > getlist($list); // GET LIST
}
public function getList($list) {
var_dump($list); // NULL
}

PHP Last Object of Method Chaining

In PHP using method chaining how would one go about supplying a functional call after the last method being called in the chain?
Also while using the same instance (see below). This would kill the idea of implementing a destructor.
The end result is a return value and functional call of private "insert()" from the defined chain properties (of course) without having to call it publicly, no matter of the order.
Note, if I echo (__toString) the methods together it would retrieve the final generated unique code which is normal behavior of casting a string.
Example below:
class object
{
private $data;
function __construct($name) {
// ... some other code stuff
}
private function fc($num) {
// some wicked code here
}
public function green($num) {
$this->data .= fc($num*10);
return $this;
}
public function red($num) {
$this->data .= fc($num*25);
return $this;
}
public function blue($num) {
$this->data .= fc($num*1);
return $this;
}
// how to get this baby to fire ?
private function insert() {
// inserting
file_put_content('test_code.txt', $this->data);
}
}
$tss = new object('index_elements');
$tss->blue(100)->green(200)->red(100); // chain 1
$tss->green(0)->red(100)->blue(0); // chain 2
$tss->blue(10)->red(80)->blue(10)->green(0); // chain 3
Chain 1, 2, and 3 would generated an unique code given all the values from the methods and supply an action, e.g. automatically inserting in DB or creating a file (used in this example).
As you can see no string setting or casting or echoing is taking place.
You could keep a list of things that needs to be initialised and whether they
have been so in this instance or not. Then check the list each time you use
one of the initialisation methods. Something like:
class O {
private $init = array
( 'red' => false
, 'green' => false
, 'blue' => false
);
private function isInit() {
$fin = true;
foreach($this->init as $in) {
$fin = $fin && $in;
}
return $fin;
}
public function green($n) {
$this->init['green'] = true;
if($this->isInit()) {
$this->insert();
}
}
public function red($n) {
$this->init['red'] = true;
if($this->isInit()) {
$this->insert();
}
}
public function blue($n) {
$this->init['blue'] = true;
if($this->isInit()) {
$this->insert();
}
}
private function insert() {
echo "whee\n";
}
}
But personally I think this would be more hassle then it's worth. Better imo
to expose your insert method and let the user of you code tell when the
initialisation is finished. So something that should be used like:
$o->red(1)->green(2)->blue(0)->insert();
-update-
If it's the case that it's impossible to predict what functions need to be called
you really do need to be explicit about it. I can't see a way around that. The reason
is that php really can't tell the difference between
$o1 = new A();
$o2 = $o1->stuff();
and
$o2 = (new A())->stuff();
In a language that allows overloading = I guess it would be possible but really
really confusing and generally not a good idea.
It is possible to move the explicit part so that it's not at the end of the call
chain, but I'm not sure if that would make you happier? It would also go against
your desire to not use another instance. It could look something like this:
class O {
public function __construct(InitO $ini) {
// Do stuff
echo "Whee\n";
}
}
class InitO {
public function red($n) {
return $this;
}
public function green($n) {
return $this;
}
public function blue($n) {
return $this;
}
}
$o = new O((new InitO())->red(10)->red(9)->green(7));
You can of course use just one instance by using some other way of wrapping
but the only ways I can think of right now would look a lot uglier.
Im with PeeHaa, this makes no sense! :)
Only chance to have something magically happen after the last chain was used (without being able to look into the future) is a Destructor/Shutdown function OR a manually cast/call to insert()
You can also decide to implement this statically without using objects.
<?php
class Object
{
private static $data;
public static function set($name)
{
// ... some other code stuff
}
private static function fc($num)
{
// some wicked code here
}
public static function green($num)
{
self::$data .= self::fc($num*10);
return new static;
}
public static function red($num)
{
self::$data .= self::fc($num*25);
return new static;
}
public static function blue($num) {
self::$data .= self::fc($num*1);
return new static;
}
// how to get this baby to fire ?
public static function insert()
{
// inserting
file_put_content('test_code.txt', self::$data);
}
}
//$tss = new object('index_elements');
$Object::set('index_elements')->blue(100)->green(200)->red(100)->insert(); // chain 1
$Object::set('index_elements')->green(0)->red(100)->blue(0)->insert(); // chain 2
$Object::set('index_elements')->blue(10)->red(80)->blue(10)->green(0)->insert(); // chain 3
?>
Ok let's see a code example
<?php
// map dummy class
class map
{
// __call magic method
public function __call($name, $args)
{
return $this;
}
}
// now we chain
$map = new map;
// let's find me
$map->start('here')
->go('right')
->then()
->turn('left')
->and('get water')
->dontEat()
->keep('going')
->youShouldSeeMe('smiling');
here we don't know what the last method would be and we need to trigger a kinda operation or event once we hit the end.
According to data structure we can call this the LIFO stack. (Last in first out)
so how did i solve this on PHP?
// i did some back tracing
... back to the __call function
function __call($name, $args)
{
$trace = debug_backtrace()[0];
$line = $trace['line'];
$file = $trace['file'];
$trace = null;
$getFile = file($file);
$file = null;
$getLine = trim($getFile[$line-1]);
$line = null;
$getFile = null;
$split = preg_split("/(->)($name)/", $getLine);
$getLine = null;
if (!preg_match('/[)](->)(\S)/', $split[1]) && preg_match('/[;]$/', $split[1]))
{
// last method called.
var_dump($name); // outputs: youShouldSeeMe
}
$split = null;
return $this;
}
And whoolla we can call anything once we hit the bottom.
*(Notice i use null once i am done with a variable, i come from C family where we manage memory ourselves)
Hope it helps you one way or the other.

PHP object method doesn't behave as I expect

I can't quite understand why the output of this code is '1'.
My guess is that php is not behaving like most other OO languages that I'm used to, in that the arrays that php uses must not be objects. Changing the array that is returned by the class does not change the array within the class. How would I get the class to return an array which I can edit (and has the same address as the one within the class)?
<?php
class Test
{
public $arr;
public function __construct()
{
$this->arr = array();
}
public function addToArr($i)
{
$this->arr[] = $i;
}
public function getArr()
{
return $this->arr;
}
}
$t = new Test();
$data = 5;
$t->addToArr($data);
$tobj_arr = $t->getArr();
unset($tobj_arr[0]);
$tobj_arr_fresh = $t->getArr();
echo count($tobj_arr_fresh);
?>
EDIT: I expected the output to be 0
You have to return the array by reference. That way, php returns a reference to the array, in stead of a copy.
<?php
class Test
{
public $arr;
public function __construct()
{
$this->arr = array();
}
public function addToArr($i)
{
$this->arr[] = $i;
}
public function & getArr() //Returning by reference here
{
return $this->arr;
}
}
$t = new Test();
$data = 5;
$t->addToArr($data);
$tobj_arr = &$t->getArr(); //Reference binding here
unset($tobj_arr[0]);
$tobj_arr_fresh = $t->getArr();
echo count($tobj_arr_fresh);
?>
This returns 0.
From the returning references subpage:
Unlike parameter passing, here you have to use & in both places - to
indicate that you want to return by reference, not a copy, and to
indicate that reference binding, rather than usual assignment, should
be done
Note that although this gets the job done, question is if it is a good practice. By changing class members outside of the class itself, it can become very difficult to track the application.
Because array are passed by "copy on write" by default, getArr() should return by reference:
public function &getArr()
{
return $this->arr;
}
[snip]
$tobj_arr = &$t->getArr();
For arrays that are object, use ArrayObject. Extending ArrayObject is probably better in your case.
When you unset($tobj_arr[0]); you are passing the return value of the function call, and not the actual property of the object.
When you call the function again, you get a fresh copy of the object's property which has yet to be modified since you added 5 to it.
Since the property itself is public, try changing:
unset($tobj_arr[0]);
To: unset($t->arr[0]);
And see if that gives you the result you are looking for.
You are getting "1" because you are asking PHP how many elements are in the array by using count. Remove count and use print_r($tobj_arr_fresh)

Categories