PHP Pipeline, why is the object getting cloned? - php

I was looking at some php code and stumbled on a pipeline script. In the method to add something to the pipeline:
public function pipe(callable $stage)
{
$pipeline = clone $this;
$pipeline->stages[] = $stage;
return $pipeline;
}
The object is getting cloned, and returned.
Could someone explain me the advantages of this approach,
wouldn't the following code return the same results?
public function pipe(callable $stage)
{
$this->stages[] = $stage;
return $this;
}

No, it won't return the same. Clone creates a copy of the object, which is sometimes the desired behaviour.
class WithoutClone {
public $var = 5;
public function pipe(callable $stage)
{
$this->stages[] = $stage;
return $this;
}
}
$obj = new WithoutClone();
$pipe = $obj->pipe(...);
$obj->var = 10;
echo $pipe->var; // Will echo 10, since $pipe and $obj are the same object
// (just another variable-name, but referencing to the same instance);
// ----
class Withlone {
public $var = 5;
public function pipe(callable $stage)
{
$pipeline = clone $this;
$pipeline->stages[] = $stage;
return $pipeline;
}
}
$obj = new WithClone();
$pipe = $obj->pipe(...);
$obj->var = 10;
echo $pipe->var; // Will echo 5, since pipe is a clone (different object);

Related

Php method chaining not working, returning an PDO error [duplicate]

I am using PHP 5 and I've heard of a new featured in the object-oriented approach, called 'method chaining'. What is it exactly? How do I implement it?
It's rather simple, really. You have a series of mutator methods that all return the original (or other) object. That way, you can keep calling methods on the returned object.
<?php
class fakeString
{
private $str;
function __construct()
{
$this->str = "";
}
function addA()
{
$this->str .= "a";
return $this;
}
function addB()
{
$this->str .= "b";
return $this;
}
function getStr()
{
return $this->str;
}
}
$a = new fakeString();
echo $a->addA()->addB()->getStr();
This outputs "ab"
Try it online!
Basically, you take an object:
$obj = new ObjectWithChainableMethods();
Call a method that effectively does a return $this; at the end:
$obj->doSomething();
Since it returns the same object, or rather, a reference to the same object, you can continue calling methods of the same class off the return value, like so:
$obj->doSomething()->doSomethingElse();
That's it, really. Two important things:
As you note, it's PHP 5 only. It won't work properly in PHP 4 because it returns objects by value and that means you're calling methods on different copies of an object, which would break your code.
Again, you need to return the object in your chainable methods:
public function doSomething() {
// Do stuff
return $this;
}
public function doSomethingElse() {
// Do more stuff
return $this;
}
Try this code:
<?php
class DBManager
{
private $selectables = array();
private $table;
private $whereClause;
private $limit;
public function select() {
$this->selectables = func_get_args();
return $this;
}
public function from($table) {
$this->table = $table;
return $this;
}
public function where($where) {
$this->whereClause = $where;
return $this;
}
public function limit($limit) {
$this->limit = $limit;
return $this;
}
public function result() {
$query[] = "SELECT";
// if the selectables array is empty, select all
if (empty($this->selectables)) {
$query[] = "*";
}
// else select according to selectables
else {
$query[] = join(', ', $this->selectables);
}
$query[] = "FROM";
$query[] = $this->table;
if (!empty($this->whereClause)) {
$query[] = "WHERE";
$query[] = $this->whereClause;
}
if (!empty($this->limit)) {
$query[] = "LIMIT";
$query[] = $this->limit;
}
return join(' ', $query);
}
}
// Now to use the class and see how METHOD CHAINING works
// let us instantiate the class DBManager
$testOne = new DBManager();
$testOne->select()->from('users');
echo $testOne->result();
// OR
echo $testOne->select()->from('users')->result();
// both displays: 'SELECT * FROM users'
$testTwo = new DBManager();
$testTwo->select()->from('posts')->where('id > 200')->limit(10);
echo $testTwo->result();
// this displays: 'SELECT * FROM posts WHERE id > 200 LIMIT 10'
$testThree = new DBManager();
$testThree->select(
'firstname',
'email',
'country',
'city'
)->from('users')->where('id = 2399');
echo $testThree->result();
// this will display:
// 'SELECT firstname, email, country, city FROM users WHERE id = 2399'
?>
Another Way for static method chaining :
class Maker
{
private static $result = null;
private static $delimiter = '.';
private static $data = [];
public static function words($words)
{
if( !empty($words) && count($words) )
{
foreach ($words as $w)
{
self::$data[] = $w;
}
}
return new static;
}
public static function concate($delimiter)
{
self::$delimiter = $delimiter;
foreach (self::$data as $d)
{
self::$result .= $d.$delimiter;
}
return new static;
}
public static function get()
{
return rtrim(self::$result, self::$delimiter);
}
}
Calling
echo Maker::words(['foo', 'bob', 'bar'])->concate('-')->get();
echo "<br />";
echo Maker::words(['foo', 'bob', 'bar'])->concate('>')->get();
Method chaining means that you can chain method calls:
$object->method1()->method2()->method3()
This means that method1() needs to return an object, and method2() is given the result of method1(). Method2() then passes the return value to method3().
Good article: http://www.talkphp.com/advanced-php-programming/1163-php5-method-chaining.html
There are 49 lines of code which allows you to chain methods over arrays like this:
$fruits = new Arr(array("lemon", "orange", "banana", "apple"));
$fruits->change_key_case(CASE_UPPER)->filter()->walk(function($value,$key) {
echo $key.': '.$value."\r\n";
});
See this article which shows you how to chain all the PHP's seventy array_ functions.
http://domexception.blogspot.fi/2013/08/php-magic-methods-and-arrayobject.html
A fluent interface allows you to chain method calls, which results in less typed characters when applying multiple operations on the same object.
class Bill {
public $dinner = 20;
public $desserts = 5;
public $bill;
public function dinner( $person ) {
$this->bill += $this->dinner * $person;
return $this;
}
public function dessert( $person ) {
$this->bill += $this->desserts * $person;
return $this;
}
}
$bill = new Bill();
echo $bill->dinner( 2 )->dessert( 3 )->bill;
I think this is the most relevant answer.
<?php
class Calculator
{
protected $result = 0;
public function sum($num)
{
$this->result += $num;
return $this;
}
public function sub($num)
{
$this->result -= $num;
return $this;
}
public function result()
{
return $this->result;
}
}
$calculator = new Calculator;
echo $calculator->sum(10)->sub(5)->sum(3)->result(); // 8
If you mean method chaining like in JavaScript (or some people keep in mind jQuery), why not just take a library that brings that dev. experience in PHP? For example Extras - https://dsheiko.github.io/extras/ This one extends PHP types with JavaScript and Underscore methods and provides chaining:
You can chain a particular type:
<?php
use \Dsheiko\Extras\Arrays;
// Chain of calls
$res = Arrays::chain([1, 2, 3])
->map(function($num){ return $num + 1; })
->filter(function($num){ return $num > 1; })
->reduce(function($carry, $num){ return $carry + $num; }, 0)
->value();
or
<?php
use \Dsheiko\Extras\Strings;
$res = Strings::from( " 12345 " )
->replace("/1/", "5")
->replace("/2/", "5")
->trim()
->substr(1, 3)
->get();
echo $res; // "534"
Alternatively you can go polymorphic:
<?php
use \Dsheiko\Extras\Any;
$res = Any::chain(new \ArrayObject([1,2,3]))
->toArray() // value is [1,2,3]
->map(function($num){ return [ "num" => $num ]; })
// value is [[ "num" => 1, ..]]
->reduce(function($carry, $arr){
$carry .= $arr["num"];
return $carry;
}, "") // value is "123"
->replace("/2/", "") // value is "13"
->then(function($value){
if (empty($value)) {
throw new \Exception("Empty value");
}
return $value;
})
->value();
echo $res; // "13"
Below is my model that is able to find by ID in the database. The with($data) method is my additional parameters for relationship so I return the $this which is the object itself. On my controller I am able to chain it.
class JobModel implements JobInterface{
protected $job;
public function __construct(Model $job){
$this->job = $job;
}
public function find($id){
return $this->job->find($id);
}
public function with($data=[]){
$this->job = $this->job->with($params);
return $this;
}
}
class JobController{
protected $job;
public function __construct(JobModel $job){
$this->job = $job;
}
public function index(){
// chaining must be in order
$this->job->with(['data'])->find(1);
}
}

How to use a array in a class which keeps it values and is acessable from outside the class

I am using php 7.4.9 and have a class which reads information from a file. These informations should be all the time availabe from outside the class and it also should possible to modify that array, so that this class can write back these information on request.
I have looked for a while but could not fined a useful solution.
I got the functions working, but the array loose the values from call to call.
Edit 2020/12/12
This is the uses structure of my code
<?php
.......
function show(){
$id3 = ID3::create();
$mp3 = &ID3::$mp3Array;
if($mode == "manual"){
if($file == ""){
return "";
}
$fName = $dir . "/" . $file;
$id3->open($fName);
.......
}else if($mode == "save"){
$fName = $dir . "/" . $file;
$id3->save($fName);
return "Save done!";
}
} // end of show
class ID3{
public static $mp3Array = array();
public static function create(): self {
static $object;
$object = $object ?? new self();
return $object;
}
function open($fName){
$mp3 = self::$mp3Array;
. // $mp3 will be filled
.........
}
function save($fName) {
$mp3 = &ID3::§mp3Array;
error_log("TagSave: ".var_export($mp3, true),0); // is always empty
foreach($mp3 as $key => $value){
........
}
}
} //end of class>
?>
If I try to save the modified array, it is always empty, if show is called again!
I have also implemented the #Logifire proposal 'create'. I got a valid pointer but the arrayis still empty.
Maybe I should point out, that it is web page. The html code sends information (form) back to the php program.
I figured out, that use of global $id3 = NULL; does not work, because the php grogramm will be always called and set the variable again to NULL each time.
I have also implemented the following code on the beginning
<?php
error_log("PHP call",0);
$id3count = 0;
if(array_key_exists("Test_id3",$GLOBALS)){
error_log("GLOBALS[Test_id3] exist!",0);
}else{
error_log("GLOBALS[Test_id3] does not exist!",0);
$GLOBALS['Test_id3'] = "NEW";
}
The $GLOBAL['Test_id3'] never exist, if the programm will be called!
I got the functions working, but the array loose the values from call to call.
As I understand you, your setup is not a long running app, you can not keep state between requests (calls).
But if you are aware of that, the issue may be you have a new instance of the class each time you call it within the same request flow, you may use a singleton if this is the case. I suggest using accessors in your class.
class MyDataList {
private array $my_array = [];
private function __construct()
{
}
public static function create(): self {
static $object;
$object = $object ?? new self();
return $object;
}
public function setArray(array $new_array): void {
$this->my_array = $new_array;
// open, write, close file..
}
public function getArray(): array {
return $this->my_array;
}
}
$my_data_list = MyDataList::create();
Based on your edited question (2020/12/12), I extended the example code:
class MyDataList {
private array $my_array = [];
private $file_path = '';
private function __construct()
{
}
public static function create(string $file_path): self {
static $object;
if ($object === null) {
$object = new self();
$stringified = file_get_contents($file_path) ?: '';
$array = json_decode($stringified, true) ?: [];
$object->file_path = $file_path;
$object->my_array = $array;
}
return $object;
}
public function setArray(array $new_array): void {
$this->my_array = $new_array;
$stringified = json_encode($new_array);
file_put_contents($this->file_path, $stringified);
}
public function getArray(): array {
return $this->my_array;
}
}
$my_data_list = MyDataList::create('/path/to/file');
Note: Be aware, you need to apply error handling
Comment answers:
Is the filepath connected to the array?
Well, you will write your data as JSON to a file each time you "modify" the array via the setArray()
Does it means, that the array is stored into a file and read out each time I try to connect again?
For each request you call create() it will instantiate the internal state of the array based on the stored data in the file. ATM. The file_get_contents call may have been wrapped and only called if the $object was not instantiated. (Now updated in the example)
So I have to call setArray($array); to save the data. I was looking for a soluting to keep the data without an management to save and read the array. Is this not possible with PHP?
Maybe you want to use a session variable to store your data? But it is individual per user and not long lived data - Link: https://www.php.net/manual/en/reserved.variables.session.php
In a standard PHP setup you can not have data/state between requests, but there are solution like Swoole which makes PHP a long running app: https://www.php.net/manual/en/book.swoole.php
I need a possibility to modify the array directly.
Is it a reference to the array you want? https://3v4l.org/OsBC6
class MyDataList {
private array $my_array = [];
private function __construct()
{
}
public static function create(): self {
static $object;
$object = $object ?? new self();
return $object;
}
public function setArray(array &$new_array): void {
$this->my_array = &$new_array;
}
public function getArray(): array {
return $this->my_array;
}
}
There is no easy way to do with PHP!
Finally I use the proposal from Logifire, but had to modified it to fullfill my requirements.
I needed more than 1 array.
One array can ibclude binary data values, which json can't handle. So I have to use base64 for the binary data values.
Here my code:
public array $mp3Array = array();
public array $findArray = array();
private $file_dir = "";
public static function create(string $fileDir): self {
static $object;
if ($object === null) {
$object = new self();
$stringified1 = file_get_contents($fileDir."/mp3Array.obj") ?: '';
$array1 = json_decode($stringified1, true) ?: [];
$stringified2 = file_get_contents($fileDir."/findArray.obj") ?: '';
$array2 = json_decode($stringified2, true) ?: [];
$object->file_dir = $fileDir;
$object->mp3Array = $object->arrayDecode($array1);
$object->findArray = $array2;
}
return $object;
}
private function arrayEncode($arr){
$tmp = [];
foreach($arr as $key => $val){
if(is_array($val)){
$tmp[$key] = $this->arrayEncode($val);
}else if ($key == "data"){
$tmp[$key] = base64_encode($val);
}else{
$tmp[$key] = $val;
}
}
return $tmp;
}
private function arrayDecode($arr){
$tmp = [];
foreach($arr as $key => $val){
if(is_array($val)){
$tmp[$key] = $this->arrayDecode($val);
}else if ($key == "data"){
$tmp[$key] = base64_decode($val);
}else{
$tmp[$key] = $val;
}
}
return $tmp;
}
public function setMp3(array $new_array): void {
$this->mp3Array = $new_array;
$stringified = json_encode($new_array);
file_put_contents($this->file_dir."/mp3Array.obj", $stringified);
}
public function saveMp3(): void {
$base64 = $this->arrayEncode($this->mp3Array);
$stringified = json_encode($base64);
file_put_contents($this->file_dir."/mp3Array.obj", $stringified);
}
public function setFind(array $new_array): void {
$this->findArray = $new_array;
$stringified = json_encode($new_array);
file_put_contents($this->file_dir."/findArray.obj", $stringified);
}
public function saveFind(): void {
$stringified = json_encode( $this->findArray);
file_put_contents($this->file_dir."/findArray.obj", $stringified);
}

Set anonymous class as parameter of a function

I am writing an small ODM for Mongodb in PHP by PhpStorm. At the aggregate query:
$value = $this->creditRepo->execute(AggregateQueryBuilder::create()->project('name', 'ts')->take(10)->toQuery(),
new class{
public ?string $name;
});
the second parameter is an anonymous class. And inside of execute() function:
function execute(AggregateQuery $query, $class): ArrayObject
{
$result = $this->_collection->aggregate($query->getPipeline(), $query->getOptions());
$returnArray = new ArrayObject();
foreach ($result as $item){
$itemx = App::jsonMapper()->map($item, new $class); // $itemx is instance of $class
$returnArray->append($itemx);
}
return $returnArray;
}
The result in caller script:
$value = $this->creditRepo->execute(AggregateQueryBuilder::create()
->project('name', 'ts')->take(10)->toQuery(), new class{
public ?string $name;
});
$d = $value->getArrayCopy();
$d[0]-> // not type hint, I want it hint to me the <name>
Can PhpStorm know the output of the execute() that I can type hint the output of it?
[Edit]
I have try with function array_map
$value = $this->creditRepo->execute(AggregateQueryBuilder::create()
->project('name', 'ts')->take(10)->toQuery());
$temp = new class{
public ?string $name;
};
$output = array_map(static function ($item) use ($temp) {
$temp->name = $item['name'];
return $temp;
}, (array)$value);
$output[0]->name // type hinted
and I want to write a function that PhpStorm know that return like array_map how to do it?

The Scoping of Static Variables in PHP Anonymous Functions

I have some trouble,When I define a static variable in a method and call it multiple times, the code is as follows:
function test()
{
static $object;
if (is_null($object)) {
$object = new stdClass();
}
return $object;
}
var_dump(test());
echo '<hr>';
var_dump(test());
The output is as follows:
object(stdClass)[1]
object(stdClass)[1]
Yes, they return the same object.
However, when I define a closure structure, it returns not the same object.
function test($global)
{
return function ($param) use ($global) {
//echo $param;
//exit;
static $object;
if (is_null($object)) {
$object = new stdClass();
}
return $object;
};
}
$global = '';
$closure = test($global);
$firstCall = $closure(1);
$closure = test($global);
$secondCall = $closure(2);
var_dump($firstCall);
echo '<hr>';
var_dump($secondCall);
The output is as follows:
object(stdClass)[2]
object(stdClass)[4]
which is why, I did not understand.
By calling test(...) twice in your sample code, you have generated two distinct (but similar) closures. They are not the same closure.
This becomes more obvious some some subtle improvements to your variable names
$closureA = test($global);
$firstCall = $closureA(1);
$closureB = test($global);
$secondCall = $closureB(2);
var_dump($firstCall, $secondCall);
Now consider this code alternative:
$closureA = test($global);
$firstCall = $closureA(1);
$secondCall = $closureA(2);
var_dump($firstCall, $secondCall);
Does that help you understand?

Consistent implementation functions in a logical order

Maybe I'm wrong to expressed it in the title, but I just do not understand how in the class like this.
<?php
class sample{
public $data = [];
public function pushIndex($index){
array_push($this->data, $index);
}
public function pushValue($value){
array_push($this->data["index"], $value);
// Some magic
}
public function forIndex($index){
return $this->data[$index];
// Some magic
}
}
To realize scheme like in Symfony, where will be spaghetti like this
<?php
$a = new sample;
$a->pushIndex("index")->pushValue("value");
$a->forIndex("index2")->pushValue("value2");
Maybe someone knows how to do it?
What you're talking about is called Fluent interface.
Returns the current object by using $this.
public function pushIndex($index){
array_push($this->a,$index);
return $this;
}
But what you want is to do something like this:
class sample
{
protected $a = [];
protected $currentIndex = null;
public function pushIndex($index)
{
$this->currentIndex = $index;
return $this;
}
public function pushValue($value)
{
if ($this->currentIndex === null) {
throw new LogicException('You need to call "pushIndex" or "forIndex" first.');
}
$this->a[$this->currentIndex] = $value;
return $this;
}
public function forIndex($index)
{
if (!isset($this->a[$index])) {
throw new RuntimeException(sprintf('Index "%s" doesn\'t exists', $index));
}
$this->currentIndex = $index;
return $this;
}
public function getArray()
{
return $this->a;
}
}
$a = new sample;
$a->pushIndex("index")->pushValue("value");
$a->forIndex("index2")->pushValue("value2"); // exception?
var_dump($a->getArray());
But what you want is pretty unclear.
I think what you're trying to achieve is something like this:
class sample{
public $a = [];
public $index = null;
public function pushIndex($index){
$this->index = $index;
$this->a[$index] = null;
return $this;
}
public function pushValue($value){
$this->a[$this->index] = $value;
return $this;
}
public function forIndex($index){
$this->index = $index;
return $this;
}
}
$a = new sample;
$a->pushIndex("index")->pushValue("value");
$a->forIndex("index2")->pushValue("value2");
echo "<pre>";
var_dump($a);
echo "</pre>";
This is called "method chaining". By returning a reference to the called object, you're able to perform further methods on the object, essentially "chaining" the methods.
I've had to adjust your code a little to get it the work I believe the way you want it to. It should provide a working example to help you understand method chaining.

Categories