PHP custom function with as syntax, function ($x as $y) - php

I'd like to implement a for loop using the $array as $element syntax in an anonymous function
Is this possible? I can't use forEach as the $array is actually an object, and forEach doesn't copy the object as it would with an array
I'm using laravel so I'm trying to write a blade directive like forEach but in a way that it makes a copy of the object.
something like:
Blade::directive('safeForEach', function ($array as $el){
return '<?php for ($i = 0; $i < count($array); $i++){ ?>';
});
where after #safeForEach is called, I can use each $el.
Blade::directive('endSafeForEach', function (){
return '<?php { ?>';
});
Any ideas please? I've tried to find the source for the blade directive but can't find it.
Thanks

The directive command's callback takes a single parameter which is the string that is passed in the directive which you need to parse. You can check the source code of Laravel and adapt it to your own needs:
Blade::directive('safeForEach', function ($expression) {
preg_match('/\( *(.*) +as *(.*)\)$/is', $expression, $matches);
$iteratee = trim($matches[1]);
$iteration = trim($matches[2]);
$initLoop = "\$__currentLoopData = {$iteratee}; \$__env->addLoop(\$__currentLoopData);";
$iterateLoop = '$__env->incrementLoopIndices(); $loop = $__env->getLastLoop();'.$iteration.'=clone '.$iteratee.'[$i]';
return '<?php '. $initLoop.' for ($i = 0; $i < count('.$iteratee.'); $i++) { '.$iterateLoop.' ?>';
});
Blade::directive('endSafeForEach', function () {
return '<?php } ?>';
});
If for example you pass #safeForEach($a as $b) this should compile to:
$__currentLoopData = $a;
$__env->addLoop($__currentLoopData);
for ($i = 0;$i < count($a);$i++) {
$__env->incrementLoopIndices();
$loop = $__env->getLastLoop();
$b = clone $a[$i];
however you need to ensure that
$iteratee is countable
$i does not conflict. Laravel uses a variable called $__env to store loop information and assumes that it's not used elsewhere but given the name it's less likely to be used.
clone will only make a shallow copy of an object and might break for non-clonable elements so you might also need to refine that part

Related

Creating a PHP array with values from a function

I want to create an array with x elements, where each element is created by calling a function. I'm looking for an idiomatic way to do this that minimizes state, especially mutating state, and also minimizes imperativeness.
I have working code that does this task, more specifically it creates an array with 10 elements using a function that returns a random character.
array_map(
function() use ( $characters, $characterCount ) {
return $characters[mt_rand( 0, $characterCount - 1 )];
},
array_fill( 0, 10, null )
)
When starting with this I was looking for something like the following, but did not find it:
array_create(
function() {},
10
);
It is common to simply use a looping construct like:
$array = array();
for($i = 0; $i < 10; $i++) {
$array[] = randomfunction();
}
If you want to wrap this in a function, do something like:
function genArray($x, $func) {
$array = array();
for($i = 0; $i < $x; $i++) {
$array[] = $func();
}
return $array;
}
Then you can pass it the number of elements and which function to use!

Building chained function calls dynamically in PHP

I use PHP (with KirbyCMS) and can create this code:
$results = $site->filterBy('a_key', 'a_value')->filterBy('a_key2', 'a_value2');
This is a chain with two filterBy. It works.
However I need to build a function call like this dynamically. Sometimes it can be two chained function calls, sometimes three or more.
How is that done?
Maybe you can play with this code?
chain is just a random number that can be used to create between 1-5 chains.
for( $i = 0; $i < 10; $i ++ ) {
$chains = rand(1, 5);
}
Examples of desired result
Example one, just one function call
$results = $site->filterBy('a_key', 'a_value');
Example two, many nested function calls
$results = $site->filterBy('a_key', 'a_value')->filterBy('a_key2', 'a_value2')->filterBy('a_key3', 'a_value3')->filterBy('a_key4', 'a_value4')->filterBy('a_key5', 'a_value5')->filterBy('a_key6', 'a_value6');
$chains = rand(1, 5)
$results = $site
$suffix = ''
for ( $i = 1; $i <= $chains; $i ++) {
if ($i != 1) {
$suffix = $i
}
$results = $results->filterBy('a_key' . $suffix, 'a_value' . $suffix)
}
If you are able to pass 'a_key1' and 'a_value1' to the first call to filterBy instead of 'a_key' and 'a_value', you could simplify the code by removing $suffix and the if block and just appending $i.
You don't need to generate the list of chained calls. You can put the arguments of each call in a list then write a new method of the class that gets them from the list and uses them to invoke filterBy() repeatedly.
I assume from your example code that function filterBy() returns $this or another object of the same class as site.
//
// The code that generates the filtering parameters:
// Store the arguments of the filtering here
$params = array();
// Put as many sets of arguments you need
// use whatever method suits you best to produce them
$params[] = array('key1', 'value1');
$params[] = array('key2', 'value2');
$params[] = array('key3', 'value3');
//
// Do the multiple filtering
$site = new Site();
$result = $site->filterByMultiple($params);
//
// The code that does the actual filtering
class Site {
public function filterByMultiple(array $params) {
$result = $this;
foreach ($params as list($key, $value)) {
$result = $result->filterBy($key, $value);
}
return $result;
}
}
If filterBy() returns $this then you don't need the working variable $result; call $this->filterBy() and return $this; and remove the other occurrences of $result.

Multiple returning

Could anyone help me.
I need to return multiple img's, but with this code, only one of two is returning.
What is the solution.
Thank you in advance.
$test = "/claim/img/box.png, /claim/img/box.png";
function test($test)
{
$photo = explode(',', $test);
for ($i = 0; $i < count($photo); $i++)
{
$returnas = "<img src=".$photo[$i].">";
return $returnas;
}
}
This might be a good opportunity to learn about array_map.
function test($test) {
return implode("",array_map(function($img) {
return "<img src='".trim($img)."' />";
},explode(",",$test)));
}
Many functions make writing code a lot simpler, and it's also faster because it uses lower-level code.
While we're on the subject of learning things, PHP 5.5 gives us generators. You could potentially use one here. For example:
function test($test) {
$pieces = explode(",",$test);
foreach($pieces as $img) {
yield "<img src='".trim($img)."' />";
}
}
That yield is where the magic happens. This makes your function behave like a generator. You can then do this:
$images = test($test);
foreach($images as $image) echo $image;
Personally, I think this generator solution is a lot cleaner than the array_map one I gave earlier, which in turn is tidier than manually iterating.
Modify your code that way
function test($test)
{
$returnas = '';
$photo = explode(',', $test);
for ($i = 0; $i < count($photo); $i++)
{
$returnas .= "<img src=".$photo[$i].">";
}
return $returnas;
}
Your code didn't work since you were returning inside the loop immediatly. Every programming language support "only a return for call". In my solution you're appendig a string that has an img tag each time you enter the loop and return it after every photo is "passed" into the loop
You could even use the foreach() construct, of course
Bonus answer
If you don't know the difference between ...
for ($i = 0; $i < count($photo); $i++)
and
for ($i = 0, $count = count($photo); $i < $<; $i++)
Well, in first case you'll evaluate count($photo) every single time the for is called whereas the second time, it is evaluated only once.
This could be used for optimization porpuses (even if php, internally, stores the length of an array so it is accesible in O(1))
The function breaks after the first return statement. You need to save what you want to return in some structure, an array eg, and return this.
function test($test)
{
$result = array();
$photo = explode(',', $test);
for ($i = 0; $i < count($photo); $i++)
{
$returnas = "<img src=".$photo[$i].">";
$result[] = $returnas;
}
return $result;
}

How to explain this behaviour from DirectoryIterator?

Can anyone explain why this happens? Side question: is there a way to make it work using DirectoryIterator so the correct objects are stored in $j and $k?
<?php
// my name is dirtest.php and I live on my own in a directory
$i = new DirectoryIterator(".");
$j = [];
$k = [];
function println($val) {
echo $val . "\n";
}
println('First time:');
foreach ($i as $x) {
$j[] = $x;
println($x->getFilename());
}
println('Second time:');
foreach ($j as $y) {
$k[] = $y->getFilename();
println($y->getFilename());
}
Expected:
First time:
.
..
dirtest.php
Second time:
.
..
dirtest.php
Actual:
First time:
.
..
dirtest.php
Second time:
First time through all seems as expected but, after storing references to each file in $j , each element appears to lose its reference. (I started out trying to use filter/map functions from this library but reduced the problem down to the listing above.)
Tested with PHP 5.4.27.
$x is an object reference of type DirectoryIterator, and as a result, all elements in $j are identical. Once the iterator has finished working, it has nothing to display.
http://www.php.net/manual/en/language.oop5.object-comparison.php
When you attribute a value of an object, the object isn't copied, but the reference to that object is attributed, so they are identical.
Now, when DirectoryIterator iterates in a foreach(), the next() method is called on each iteration, so your $x is the same reference each time, but the object has changed. But all the references still point to the same object, which, in the end, after the iterations have ended, doesn't refer to any file.
For fun, you can do a:
for($i=1;$i<count($j);$i++) {
var_dump($j[$i-1]===$j[$i]); echo "\n";
}
you should get all lines bool(true)
What I really wanted to do was along the lines of this:
<?php
use Functional as F;
$i = new DirectoryIterator('.');
$j = F\filter($i, function ($x) { return $x->isDot(); });
looks good, but $j is not what you might think!
But I can add an extra step...
<?php
use Functional as F;
$i = new DirectoryIterator('.');
function deref($xs) {
return F\map($i, function ($x) { return clone $x; });
}
$j = F\filter(
deref($i),
function ($x) { return $x->isDot(); })
);
$j is now a cool filtered beer array containing my directory entries.

PHP each and static array declaration

So, I've written some rather convoluted 'functional' PHP code to perform folding on an array. Don't worry, I won't use it anywhere. The problem is, PHP's 'each' function only seems to go as far as the end of an array as it is statically (actually, see bottom) declared.
// declare some arrays to fold with
$six = array("_1_","_2_","_3_","_4_","_5_","_6_");
// note: $ns = range(0,100) won't work at all--lazy evaluation?
$ns = array(1,2,3,4,5,6,7,8);
$ns[8] = 9; // this item is included
// add ten more elements to $ns. each can't find these
for($i=0; $i<10; ++$i)
$ns[] = $i;
// create a copy to see if it fixes 'each' problem
$ms = $ns;
$ms[0] = 3; // Just making sure it's actually a copy
$f = function( $a, $b ) { return $a . $b; };
$pls = function( $a, $b ) { return $a + $b; };
function fold_tr( &$a, $f )
{
$g = function ( $accum, &$a, $f ) use (&$g)
{
list($dummy,$n) = each($a);
if($n)
{
return $g($f($accum,$n),$a,$f);
}
else
{
return $accum;
}
};
reset($a);
return $g( NULL, $a, $f );
}
echo "<p>".fold_tr( $six, $f )."</p>"; // as expected: _1__2__3__4__5__6_
echo "<p>".fold_tr( $ns, $pls )."</p>"; // 45 = sum(1..9)
echo "<p>".fold_tr( $ms, $pls )."</p>"; // 47 = 3 + sum(2..9)
I honestly have no clue how each maintains its state; it seems vestigial at best, since there are better (non-magical) mechanisms in the language for iterating through a list, but does anyone know why it would register items added to an array using $a[$index] = value but not '$a[] = value`? Thanks in advance any insight on this behavior.
Your loop is exiting early thanks to PHP's weak typing:
if($n)
{
return $g($f($accum,$n),$a,$f);
}
else
{
return $accum;
}
when $n is 0 (e.g. $ns[9]), the condition will fail and your loop will terminate. Fix with the following:
if($n !== null)

Categories