I'm pretty new and need guidance on how to approach a simple problem.
I'm building an app in PHP to allow different views of Fantasy Premier League (FPL) team data. I'm pulling data from an API (JSON array), looping through it, and storing it in a db. One of the API calls needs a team_id to pull that team's respective players.
My initial thinking is to write a function that takes the team_id as an argument, then parses the data to insert into the db. But how would I pass each of 12 different team_ids to the function to be processed without repeating the function 12 times for each team_id?
So, I might have:
$team_id_1 = 1;
$team_id_2 = 2;
$team_id_3 = 3;
etc...
Then my function might be:
function insert_team_data($team_id) {
$fplUrl = 'https://fantasy.premierleague.com/api/entry/' . $team_id . '/event/29/picks/';
foreach ($team_data as $key => $value) {
# code...
}
$sql = "INSERT INTO ..."
}
Is it possible to construct this in a way where each team_id gets passed to the function iteratively in a single process (i.e. without repeating the function code for each team_id)? Would I create an array of the team_ids and have the function loop through each one, then loop through each resulting team's data?
Yes, you can do either: use an array or a variadic function.
The way you're thinking of doing it is what's called a variadic function or variable-length function.
It can be achieved through either the use of func_get_args() or the splat operator, which handles argument packing/unpacking.
Here's an example.
function insert_team_data(... $team_ids) {
// $team_ids will appear as an array in your function
foreach ($team_ids as $team_id) {
$fplUrl = "https://fantasy.premierleague.com/api/entry/$team_id/event/29/picks/";
$sql = "INSERT INTO ..."
// rest of code...
}
}
You can now call the function like this: insert_team_data(1, 2, 3, 4, 5)
Related
I have this code :
{...}
$data = $sql->QueryDB("SELECT fdNum AS Num, fdUserNum AS User FROM my_table
ORDER BY fdNum DESC LIMIT 100;")->fetch_all(MYSQLI_ASSOC);
echo json_encode($data);
I get this array as a result:.
[
{"Num":"195993","User":"345"},
{"Num":"195992","User":"234"},
{"Num":"195991","User":"845"}
]
But I want to use a function for the User Number(fdUserNum AS User) before outputting, it's a simple function called getUsername() and simply finds the username given the user number in the array.
So if I am to put the function somewhere in the output array, it'd be here :
[
{"Num":"195993","User":"getUsername(345)"},
{"Num":"195992","User":"getUsername(234)"},
{"Num":"195991","User":"getUsername(845)"}
]
But I can't seem to understand how to dissect the array like that and I haven't had luck googling it.
Do you know how is this achievable ?
If the Username is also stored in the database, you'd want to do it through a join in SQL:
SELECT
user.username,
...
FROM
my_table, user
WHERE
my_table.fdUserNum = user.userNum
...
If you have the username stored in the application itself or in an external service (in that case, be aware that you'll be making 100 requests to that service and would probably be better off by batching the requests), a getUsername() function should work fine:
foreach ($data as &$row) {
$row['User'] = getUsername($row['User']);
}
unset($row);
The & makes the row as a reference, allowing you to change the content of the inner array directly. The unset after the loop is to make sure you don't accidentally change the last $row after the loop.
You can also do it through the index:
foreach ($data as $idx => $row) {
$data[$idx]['User'] = getUsername($row['User']);
}
You can also use array_map by populating the key and returning the resulting array, effectively replacing the old one in the array:
function populateUsername($row) {
$row['User'] = getUsername($row['User']);
}
$data = array_map('populateUsername', $data);
I have a very large array in PHP (5.6), generated dynamically, which I want to convert to JSON. The problem is that the array is too large that it doesn't fit in memory - I get a fatal error when I try to process it (exhausted memory). So I figured out that, using generators, the memory problem will disappear.
This is the code I've tried so far (this reduced example obvisously doesn't produce the memory error):
<?php
function arrayGenerator()// new way using generators
{
for ($i = 0; $i < 100; $i++) {
yield $i;
}
}
function getArray()// old way, generating and returning the full array
{
$array = [];
for ($i = 0; $i < 100; $i++) {
$array[] = $i;
}
return $array;
}
$object = [
'id' => 'foo',
'type' => 'blah',
'data' => getArray(),
'gen' => arrayGenerator(),
];
echo json_encode($object);
But PHP seems to not JSON-encode the values from the generator. This is the output I get from the previuos script:
{
"id": "foo",
"type": "blah",
"data": [// old way - OK
0,
1,
2,
3,
//...
],
"gen": {}// using generator - empty object!
}
Is it possible to JSON-encode an array produced by a generator without generating the full sequence before I call to json_encode?
Unfortunately, json_encode cannot generate a result from a generator function. Using iterator_to_array will still try to create the whole array, which will still cause memory issues.
You will need to create your function that will generate the json string from the generator function. Here's an example of how that could look:
function json_encode_generator(callable $generator) {
$result = '[';
foreach ($generator as $value) {
$result .= json_encode($value) . ',';
}
return trim($result, ',') . ']';
}
Instead of encoding the whole array at once, it encodes only one object at a time and concatenates the results into one string.
The above example only takes care of encoding an array, but it can be easily extended to recursively encoding whole objects.
If the created string is still too big to fit in the memory, then your only remaining option is to directly use an output stream. Here's how that could look:
function json_encode_generator(callable $generator, $outputStream) {
fwrite($outputStream, '[');
foreach ($generator as $key => $value) {
if ($key != 0) {
fwrite($outputStream, ',');
}
fwrite($outputStream, json_encode($value));
}
fwrite($outputStream, ']');
}
As you can see, the only difference is that we now use fwrite to write to the passed in stream instead of concatenating strings, and we also need to take care of the trailing comma in a different way.
What is a generator function?
A generator function is effectively a more compact and efficient way to write an Iterator. It allows you to define a function that will calculate and return values while you are looping over it:
Also as per document from http://php.net/manual/en/language.generators.overview.php
Generators provide an easy way to implement simple iterators without the overhead or complexity of implementing a class that implements the Iterator interface.
A generator allows you to write code that uses foreach to iterate over a set of data without needing to build an array in memory, which may cause you to exceed a memory limit, or require a considerable amount of processing time to generate. Instead, you can write a generator function, which is the same as a normal function, except that instead of returning once, a generator can yield as many times as it needs to in order to provide the values to be iterated over.
What is yield?
The yield keyword returns data from a generator function:
The heart of a generator function is the yield keyword. In its simplest form, a yield statement looks much like a return statement, except that instead of stopping execution of the function and returning, yield instead provides a value to the code looping over the generator and pauses execution of the generator function.
So in your case to generate expected output you need to iterate output of arrayGenerator() function by using foreach loop or iterator before processind it to json (as suggested by #apokryfos)
I am making an application that pulls data through JSON with CURL. I'm using PHP and my script currently creates 2 different objects to contain the curl requests. I've got the data in a readable format, but I need to divide an average from the first call by an average from the second call.
This average is of equal relevance to each of the calls - and I'm new to working with objects and classes so am struggling to understand how to structure this in the right way. The following line of code doesn't work - but, if you read on it will hopefully help to explain what I'm trying to do.
$yield = object1->calculate_average($list)->average['price']/object2->calculate_average($list)->average['price'];
I want to code the $yield variable in to the listing class as a further method that will display when I call $anobject->yield. Should I access this via a) object1, b) object2, or c) it's own object (which seems excessive..as it will only be used occasionally). Is there a right way to do this, or does it not really matter?
My query is general (what is the best approach to using objects when an object appears to be as relevant to all options, and is it possible to reference arrays like this: calculate_average($list)->average['price']) as much as it is specific (how can I fix this script)
When this has come up before I've tended to just regrab the data from the source and fudge it, but because it's a JSON call and therefore takes a bit longer I've been prompted to find a better solution...
A simplified version of my best effort so far is below.
I've assumed that I need to use foreach on the json outputs:
class listing {
//This takes a stdClass object from the JSON and prepares it to be served
public function output_list($data) {
$price = "0";
$vals = "0";
$list = array();
foreach ($data->listing as $item) {
//Calculate average price
$price = $price + $item->price;
$vals = $vals + 1;
//Display information about each item
$list[$vals][] = "Price: £" . $item->price . " and various data";
$list[$vals][] = ...;
}
return $list;
}
// This function calculates equations on the data which I read above
public function calculate_average($list) {
$average = array();
$total_price = array_column($list, '1'); //Find the total sum of list[?][1] which = price
$vals = count($list); //count the number of values in the list
$average['denominator'] = $vals;
$average['week'] = $total_price / $average['denominator'];
//And a number of other calculations within the array...
return $average;
}
}
I then want to call these by using the following code:
$object1 = new listing();
$list1 = $object1->output_list($response); ////Output formatted data
print_r($list1);
$average = $object1->calculate_average($list);
$object2 = new listing();
$list2 = $object2->output_list($response); ////Output formatted data
print_r($list2);
$average = $object2->calculate_average($list);
The other thing is that $total_price = array_column($list,'1') needs me to have php 5.5 - which I don't have, so I need to find an alternative - and that makes me question whether my array approach is the right one in the first place. [I need to sum every [?][1] reference in the multi-dimensional array I've made].
If you're able to help I'd be so grateful - I've been trying to get my head around objects and classes for weeks!
I'm new to web developing.
This is part of a phone service, and I'm trying to filter through 3 different arrays that are filled with strings from three database searches: $sfaa, $sfipc, and $sfuaa. I have to filter the three database arrays to locate available customer service agents. The output would be an array filled with the IVR_Number to dial.
Heres an example of the string: "'Id', 'IVR_Number', 'Market_Id'"
I have to explode the string in order to get my data from each value in the arrays. Then based on a one-to-many id in each string I have to check if the id from $sfaa is in $sfipc or $sfuaa. If not then I have to build an array with the filtered records, from there I have to locate a value from the exploded string in $sfaa that belongs to that id. I wrote the following code but theres got to be an easier way?? I hope.... The client has to wait for these results before moving forward. There is usually only 10 or 15 records.
This code works I'm just wondering if there is an easier way to do this
Any tips
// formalua needed to filter above results and fill $aadl array
// explode each active agent array
$activeagentsfec=0;
$aaivra= array();
$aaida= array();
foreach ($sfaa as $aavalue)
{
${'aadetails'.$activeagentsfec} = explode("'",$aavalue);
${'aaivr'.$activeagentsfec} = ${'aadetails'.$activeagentsfec}[5];
${'aaid'.$activeagentsfec} = ${'aadetails'.$activeagentsfec}[1];
array_push($aaivra, ${'aaivr'.$activeagentsfec});
array_push($aaida,${'aaid'.$activeagentsfec});
$activeagentsfec++;
}
// explode each inprogress call array
$activecallsfec=0;
$actida= array();
$acfida= array();
foreach ($sfipc as $acvalue)
{
${'acdetails'.$activecallsfec} = explode("'",$acvalue);
${'actid'.$activecallsfec} = ${'acdetails'.$activecallsfec}[5];
${'acfid'.$activecallsfec} = ${'acdetails'.$activecallsfec}[7];
array_push($actida, ${'actid'.$activecallsfec});
array_push($acfida, ${'acfid'.$activecallsfec});
$activecallsfec++;
}
// explode each unvailable agent
$unavailableagentsfec=0;
$uaaida= array();
foreach ($sfuaa as $uavalue)
{
${'uadetails'.$unavailableagentsfec} = explode("'",$uavalue);
${'uaaid'.$unavailableagentsfec} = ${'uadetails'.$unavailableagentsfec}[3];
array_push($uaaida, ${'uaaid'.$unavailableagentsfec});
$unavailableagentsfec++;
}
// create available agent array by id
$aaafec=0;
$aada= array();
foreach ($aaida as $aaidavalue)
{
if (in_array($aaidavalue,$actida,true))
$aaafec++;
elseif(in_array($aaidavalue,$acfida,true))
$aaafec++;
elseif(in_array($aaidavalue,$uaaida,true))
$aaafec++;
else
array_push($aada, $aaidavalue);
}
// available agent arry by ivr
$aadl= array();
foreach ($aada as $aadavalue)
{
$aaaivrsv= array_search($aadavalue,$aaida,true);
array_push($aadl,$aaivra[$aaaivrsv]);
}
Given what you were saying in the comments, I'll try to give you some useful thoughts...
You carry out much the same process to parse $sfaa, $sfipc, and $sfuaa - explode, get certain columns. If you had some way to abstract that process, with a generic function for the parsing, that returns the data in a better format, called three times on each array, you'd see better through your code.
In the same way, your process is tightly coupled to the current state of the data - e.g. ${'acdetails'.$activecallsfec}[5]; is your fifth item today, but will it always be? Something generic, where you seek an column by name, might save you a lot of trouble...
finally, when merging data, if the data is sorted before hand the merge can be a lot quicker - seeking N items in a list of M, with an unsorted list takes O(n*m) operations, but if both are sorted it's O(min(m,n)).
I've taken the time to go through your code... Unless you're usign some of its variables elsewhere, here is a shorter equivalent:
// formula needed to filter above results and fill $aadl array
// explode each active agent array
$aaivra= array();
$aaida= array();
foreach ($sfaa as $aavalue)
{
$a = explode("'",$aavalue);
array_push($aaivra, $a[5]);
array_push($aaida,$a[1]);
}
// explode each inprogress call array
$actida= array();
$acfida= array();
foreach ($sfipc as $acvalue)
{
$a = explode("'",$acvalue);
array_push($actida, $a[5]);
array_push($acfida, $a[7]);
}
// explode each unvailable agent
$uaaida= array();
foreach ($sfuaa as $uavalue)
{
$a= explode("'",$uavalue);
array_push($uaaida, $a[3]);
}
// create available agent array by id
$aada= array();
foreach ($aaida as $aaidavalue)
{
if (!in_array($aaidavalue,$actida,true) &&
!in_array($aaidavalue,$acfida,true) &&
!in_array($aaidavalue,$uaaida,true))
array_push($aada, $aaidavalue);
}
// available agent arry by ivr
$aadl= array();
foreach ($aada as $aadavalue)
{
$aaaivrsv= array_search($aadavalue,$aaida,true);
array_push($aadl,$aaivra[$aaaivrsv]);
}
I have 2 sets of arrays:
$dates1 = array('9/12','9/13','9/14','9/15','9/16','9/17');
$data1 = array('5','3','7','7','22','18');
// for this dataset, the value on 9/12 is 5
$dates2 = array('9/14','9/15');
$data2 = array('12','1');
As you can see the 2nd dataset has fewer dates, so I need to "autofill" the reset of the array to match the largest dataset.
$dates2 = array('9/12','9/13','9/14','9/15','9/16','9/17');
$data2 = array('','','12','1','','');
There will be more than 2 datasets, so I would have to find the largest dataset, and run a function for each smaller dataset to properly format it.
The function I'd create is the problem for me. Not even sure where to start at this point. Also, I can format the date and data arrays differently (multidimensional arrays?) if for some reason that is better.
You can do this in a pretty straightforward manner using some array functions. Try something like this:
//make an empty array matching your maximum-sized data set
$empty = array_fill_keys($dates1,'');
//for each array you wish to pad, do this:
//make key/value array
$new = array_combine($dates2,$data2);
//merge, overwriting empty keys with data values
$new = array_merge($empty,$new);
//if you want just the data values again
$data2 = array_values($new);
print_r($data2);
It would be pretty easy to turn that into a function or put it into a for loop to operate on your array sets. Turning them into associative arrays of key/value pairs would make them easier to work with too I would think.
If datas are related will be painful to scatter them on several array.
The best solution would be model an object with obvious property names
and use it with related accessor.
From your question I haven't a lot of hint of what data are and then I have to guess a bit:
I pretend you need to keep a daily log on access on a website with downloads. Instead of using dates/data1/data2 array I would model a data structure similar to this:
$log = array(
array('date'=>'2011-09-12','accessCount'=>7,'downloadCount'=>3),
array('date'=>'2011-09-13','accessCount'=>9), /* better downloadsCount=>0 though */
array('date'=>'2011-09-15','accessCount'=>7,'downloadCount'=>3)
...
)
Using this data structure I would model a dayCollection class with methods add,remove,get,set, search with all methods returning a day instance (yes, the remove too) and according signature. The day Class would have the standard getter/setter for every property (you can resolve to magic methods).
Depending on the amount of data you have to manipulate you can opt to maintain into the collection just the object data (serialize on store/unserialize on retrieve) or the whole object.
It is difficult to show you some code as the question is lacking of details on your data model.
If you still want to pad your array than this code would be a good start:
$temp = array($dates, $data1, $data2);
$max = max(array_map('count',$temp));
$result = array_map( function($x) use($max) {
return array_pad($x,$max,0);
}, $temp);
in $result you have your padded arrays. if you want to substitute your arrays do a simple
list($dates, $data1, $data2) = array_map(....
You should use hashmaps instead of arrays to associate each date to a data.
Then, find the largest one, cycle through its keys with a foreach, and test the existence of the same key in the small one.
If it doesn't exist, create it with an empty value.
EDIT with code (for completeness, other answers seem definitely better):
$dates_data1 = array('9/12'=>'5', '9/13'=>'3', '9/14'=>'7' /* continued */);
$dates_data2 = array('9/14'=>'12', '9/15'=>'1');
#cycle through each key (date) of the longest array
foreach($dates_data1 as $key => $value){
#check if the key exists in the smallest and add '' value if it does not
if(!isset( $date_data2[$key] )){ $date_data2[$key]=''; }
}