My question stems from a model I am writing to construct queries from predefined search objects that contain 'criteria', each search has a property $search->criteria that is an array of criteria objects....
Criteria (
"name" => "name", //name of given field to be searched
"expr" => "expr", //could be "<=" ">=" "="
"s_value" => "value" //value to be searched with
)
and the part of my search function that is adding the proper where statements to the query...
if(count($criteria)) {
foreach($criteria as $crit) {
$this->{$crit['name']}($crit['s_value'],$crit['expr']);
}
}
And now finally the function that loop is calling, 'name' here corresponds with whatever the criteria object has set as $criteria['name']....
function name($value,$expr = '=') {
$this->db->where('specific_field_name '.$expr,$value);
}
Now for the question..
I want to create a variable inside 'name' that will persist beyond a single execution, so for instance, if I have 2 criteria with the same name and it executes twice, I want to maintain a variable in it's scope for multiple executions.
EDIT
What I WANT to do. I have multple functions like this that all need their own counters.
function name($value,$expr = '=') {
if(isset($count))
$this->db->or_where('specific_field_name '.$expr,$value);
$count++;
}
else {
$count = 1;
$this->db->where('specific_field_name '.$expr,$value);
}
}
Ideas?
SOLUTION
if(count($criteria)) {
$criteria_count = array()
foreach($criteria as $crit) {
if(isset($criteria_count[$crit['name']])) {
$criteria_count[$crit['name']]++;
}
else {
$criteria_count[$crit['name']] = 1;
}
$this->{$crit['name']}($crit['s_value'],$crit['expr'],$criteria_count[$crit['name']]);
}
}
Changed the main search function to maintain a $criteria_count array with the names as keys and passing the count down to the specific functions.
You could use the global statement if you really want to do it this way.
Edit: here is how I might pass count into name and keep track of it that way.
At beginning of search function: $count = 0;
if(count($criteria)) {
foreach($criteria as $crit) {
$count = $this->{$crit['name']}($crit['s_value'],$crit['expr'],$count);
}
}
And then the name function:
function name($value,$expr = '=',$count) {
if($count > 0)
$this->db->or_where('specific_field_name '.$expr,$value);
$count++;
}
else {
$count = 1;
$this->db->where('specific_field_name '.$expr,$value);
}
return $count;
}
Related
So I have a function that currently has a foreach and it works amazing, but I'm being forced to change it to a while loop:
PLEASE NOTE: The developers at my company don't want to use the foreach and they think that a while loop would be more efficient, but I'm not understanding how that would be executed, so I need some help.
So I have the following function ($post_blocks is an array of arrays):
public function parse_block_data(string $block_name, string $selector, $post_id)
{
if (!has_blocks($post_id)) {
return false;
}
$post_blocks = parse_blocks(get_the_content('', false, $post_id));
foreach ($post_blocks as $block) {
if ($block_name != $block['blockName']) {
continue;
}
if (!isset($block['attrs']['id'])) {
return false;
}
if (isset($block['attrs']['data'][$selector])) {
return $block['attrs']['data'][$selector];
} else {
break;
}
}
return false;
}
It uses the parameters to build up an array as shown below:
Output
So I started building a while loop inside the function, but I'm clueless on how to achieve it without using a foreach or if it's even possible, so I replaced the foreach with:
// I get the 9 counts of $post_blocks correctly.
$block = 0;
while ($block < count($post_blocks))
// If the $block_name doesn't match `blockName` value inside the multi-dimensional array, then continue iterating until the end and then return false.
// If ['attrs']['id'] is not set, return false.
// At last, if we have a blockName and a ID and the selector is set, return ['attrs']['data'][$selector]
}
All help will be appreciated! It makes no sense to me, but if someone can assist, I'd be forever grateful!
It's basically the same as your foreach loop, you just set the iteration variable by indexing the array, and increment the index manually.
$block_num = 0;
while ($block_num < count($post_blocks)) {
$block = $post_blocks[$block_num];
if ($block_name == $block['blockName']) {
if (!isset($block['attrs']['id'])) {
return false;
}
if (isset($block['attrs']['data'][$selector])) {
return $block['attrs']['data'][$selector];
} else {
break;
}
}
$block_num++;
}
I'm not sure why your colleagues think this is preferable.
If there's a company coding style they want you to follow, why don't you ask them what it should be?
I need to return family data (parents, siblings and partners) for 'x' number of generations (passed as $generations parameter) starting from a single person (passed as $id parameter). I can't assume two parents, this particular genealogy model has to allow for a dynamic number of parents (to allow for biological and adoptive relationships). I think my recursion is backwards, but I can't figure out how.
The code below is triggering my base clause 5 times, once for each generation, because $generation is being reduced by 1 not for every SET of parents but for every parent. What I want is for the base clause ($generations == 0) to only be triggered once, when 'x' number of generations for all parents of the initial person are fetched.
public function fetchRelationships($id = 1, $generations = 5, $relationships = array())
{
$perId = $id;
if ($generations == 0) {
return $relationships;
} else {
$parents = $this->fetchParents($perId);
$relationships[$perId]['parents'] = $parents;
$relationships[$perId]['partners'] = $this->fetchPartners($perId);
if (!empty($parents)) {
--$generations;
foreach ($parents as $parentRel) {
$parent = $parentRel->getPer2();
$pid = $parent->getId();
$relationships[$perId]['siblings'][$pid] = $this->fetchSiblings($perId, $pid);
$perId = $pid;
$relationships[$perId] = $this->fetchRelationships($perId, $generations, $relationships);
}
}
return $relationships;
}
}
The methods fetchPartners, fetchParents and fetchSiblings just fetch the matching entities. So I am not pasting them here. Assuming that there are 2 parents, 5 generations and each generation has 2 parents then the return array should contain 62 elements, and should only trigger the base clause once those 62 elements are filled.
Thanks, in advance, for any help.
-----------Edit--------
Have rewritten with fetchSiblings and fetchPartners code removed to make it easier to read:
public function fetchRelationships($id = 1, $generations = 5, $relationships = array())
{
$perId = $id;
if ($generations == 0) {
return $relationships;
} else {
$parents = $this->fetchParents($perId);
$relationships[$perId]['parents'] = $parents;
if (!empty($parents)) {
--$generations;
foreach ($parents as $parentRel) {
$perId = $parentRel->getPer2()->getId();
$relationships[$perId] = $this->fetchRelationships($perId, $generations, $relationships);
}
}
return $relationships;
}
}
Garr Godfrey got it right. $generations will equal zero when it reaches the end of each branch. So you'll hit the "base clause" as many times as there are branches. in the foreach ($parents as $parentRel) loop, you call fetchRelationships for each parent. That's two branches, so you'll have two calls to the "base clause". Then for each of their parents, you'll have another two calls to the "base clause", and so on...
Also, you're passing back and forth the relationships, making elements of it refer back to itself. I realize you're just trying to retain information as you go, but you're actually creating lots of needless self-references.
Try this
public function fetchRelationships($id = 1, $generations = 5)
{
$perId = $id;
$relationships = array();
if ($generations == 0) {
return $relationships;
} else {
$parents = $this->fetchParents($perId);
$relationships[$perId]['parents'] = $parents;
if (!empty($parents)) {
--$generations;
foreach ($parents as $parentRel) {
$perId = $parentRel->getPer2()->getId();
$relationships[$perId] = $this->fetchRelationships($perId, $generations);
}
}
return $relationships;
}
}
you'll still hit the base clause multiple times, but that shouldn't matter.
you might be thinking "but then i will lose some of the data in $relationships", but you won't. It's all there from the recursive returns.
If you're pulling this out of a database, have you considered having the query do all of the leg work for you?
Not sure how you need the data stacked or excluded, but here's one way to do it:
<?php
class TreeMember {
public $id;
// All three should return something like:
// array( $id1 => $obj1, $id2 => $obj2 )
// and would be based on $this->$id
public function fetchParents(){ return array(); }
public function fetchPartners(){ return array(); };
public function fetchSiblings(){ return array(); };
public function fetchRelationships($generations = 5)
{
// If no more to go
if ($generations == 0) { return; }
$branch = array();
$branch['parents'] = $this->fetchParents();
$branch['partners'] = $this->fetchPartners();
$branch['partners'] = $this->fetchSiblings();
// Logic
$generations--;
foreach($branch as $tmType, $tmArr)
{
foreach($tmArr as $tmId => $tmObj)
{
$branch[$tmType][$tmId] =
$mObj->fetchRelationships
(
$generations
)
);
});
return array($this->id => $branch);
}
}
Here's an over-simplified example that doesn't work for me. How (using this method, I know there are better ways if I were actually wanting this specific result), can I get the total number of users?
User::chunk(200, function($users)
{
return count($users);
});
This returns NULL. Any idea how I can get a return value from the chunk function?
Edit:
Here might be a better example:
$processed_users = DB::table('users')->chunk(200, function($users)
{
// Do something with this batch of users. Now I'd like to keep track of how many I processed. Perhaps this is a background command that runs on a scheduled task.
$processed_users = count($users);
return $processed_users;
});
echo $processed_users; // returns null
I don't think you can achieve what you want in this way. The anonymous function is invoked by the chunk method, so anything you return from your closure is being swallowed by chunk. Since chunk potentially invokes this anonymous function N times, it makes no sense for it to return anything back from the closures it invokes.
However you can provide access to a method-scoped variable to the closure, and allow the closure to write to that value, which will let you indirectly return results. You do this with the use keyword, and make sure to pass the method-scoped variable in by reference, which is achieved with the & modifier.
This will work for example;
$count = 0;
DB::table('users')->chunk(200, function($users) use (&$count)
{
Log::debug(count($users)); // will log the current iterations count
$count = $count + count($users); // will write the total count to our method var
});
Log::debug($count); // will log the total count of records
$regions = array();
Regions::chunk(10, function($users) use (&$regions ) {
$stickers = array();
foreach ($users as $user)
{
$user->sababu = ($user->region_id > 1)? $user->region_id : 0 ;
$regions[] = $user;
}
});
echo json_encode($regions);
Use this custom function to get return value from chunked data
function iterateRecords($qb, int $count = 15)
{
$page = 1;
do {
$results = $qb->forPage($page, $count)->get();
$countResults = $results->count();
if ($countResults == 0) {
break;
}
foreach ($results as $row) {
yield $row;
}
unset($results);
$page++;
} while ($countResults == $count);
}
How to use it
$qb = User::select();
$users = iterateRecords($qb, 100);
foreach ($users as $user) {
echo $user->id;
}
Total Users Count $totalUsersCount = $qb->count();
I have a question about a recursive PHP function.
I have an array of ID’s and a function, returning an array of „child id’s“ for the given id.
public function getChildId($id) {
…
//do some stuff in db
…
return childids;
}
One childid can have childids, too!
Now, I want to have an recursive function, collecting all the childids.
I have an array with ids like this:
$myIds = array("1111“,"2222“,"3333“,“4444“,…);
and a funktion:
function getAll($myIds) {
}
What I want: I want an array, containing all the id’s (including an unknown level of childids) on the same level of my array. As long as the getChildId($id)-function is returning ID’s…
I started with my function like this:
function getAll($myIds) {
$allIds = $myIds;
foreach($myIds as $mId) {
$childids = getChildId($mId);
foreach($childids as $sId) {
array_push($allIds, $sId);
//here is my problem.
//what do I have to do, to make this function rekursive to
//search for all the childids?
}
}
return $allIds;
}
I tried a lot of things, but nothing worked. Can you help me?
Assuming a flat array as in your example, you simply need to call a function that checks each array element to determine if its an array. If it is, the function calls it itself, if not the array element is appended to a result array. Here's an example:
$foo = array(1,2,3,
array(4,5,
array(6,7,
array(8,9,10)
)
),
11,12
);
$bar = array();
recurse($foo,$bar);
function recurse($a,&$bar){
foreach($a as $e){
if(is_array($e)){
recurse($e,$bar);
}else{
$bar[] = $e;
}
}
}
var_dump($bar);
DEMO
I think this code should do the trick
function getAll($myIds) {
$allIds = Array();
foreach($myIds as $mId) {
array_push($allIds, $mId);
$subids = getSubId($mId);
foreach($subids as $sId) {
$nestedIds = getAll($sId);
$allIds = array_merge($allIds, $nestedIds);
}
}
return $allIds;
}
I am passing the two var's by ref to change them, and once I have changed them or one of the (8 loops) have found a positive in the string I am using, I want to exit the function, but I don't need to return anything because they are passed by ref.
I could just pass a copy of one and then ref the other one and set the var of the one that is copied = to the function and return that, but is there a cleaner way where I just call the function, the vars are set and I can move on?
function get_cat_size($urlstr, &$cat, &$size){ return null; };
$cat = get_cat_size($urlstr, &$size);
Does the first one work or not? Witch is better for readability?
Thanks for the input!
while( $i < $countz )
{
$pos = strpos($outdoor, $outdoor[$i]);
if($pos != false)
{
$cat = $outdoorID;
while( $j < $sizeArrayCount)
{
$poz = strpos($outdoor, $outdoor[$i]);
if($poz != false)
{
$size = $outdoorID;
return;
}
$j++;
}
return;
}
$i++;
}
^ so this should work yes no maybe so?
So this is one of 8 loops set up in a order because they are least important to important, with different var = different stores.
You can just return without a value:
function returnsNothing (&$a, &$b) {
return;
}
Or simpler just omit the return statement at all
function returnsNothing (&$a, &$b) {
// do something
}
Both snippets will make the function returning NULL.
Take a look at break for returning out of your for loops.
I would personally avoid returning null within a function since NULL will be returned by default when there is no return value specified. You can read more here at PHP: Returning values