I am making seeders for a M:M relation where I would like to attach 1 Widget to WorkspaceItem in 90% of cases, other 5% 2, last 5% 3.
$widgets = Widget::all();
$workspaceItems = WorkspaceItem::all();
foreach ($workspaceItems as $workspaceItem) {
$numberBetween = $faker->numberBetween(0, 100);
if ($numberBetween > 95) {
$widgetsToSeed = $widgets->random(3);
} else if ($numberBetween > 90 && $numberBetween <= 95) {
$widgetsToSeed = $widgets->random(2);
} else {
$widgetsToSeed = $widgets->random();
}
foreach ($widgetsToSeed as $widget) {
$workspaceItem->widgets()->attach($widget->id, [...]);
}
}
Note: I can't use sync() because I have additional properties for pivot table.
If I dd($widgetsToSeed), I indeed get random widgets. But as soon as it enters the loop and I dd($widget) I don't get the model, but just true. What seems to be the problem?
I think you should be able to just do:
$workspaceItem->widgets()->saveMany($widgetsToSeed);
And by that you don't even need the last foreach loop. To always receive a collection (even with only one element), you can also add random(1) in your last else statement. With some minor simplifications it might look like this:
foreach ($workspaceItems as $workspaceItem) {
$numberBetween = $faker->numberBetween(0, 100);
if ($numberBetween > 95) {
$widgetsToSeed = $widgets->random(3);
} else if ($numberBetween > 90) {
$widgetsToSeed = $widgets->random(2);
} else {
$widgetsToSeed = $widgets->random(1);
}
$workspaceItem->widgets()->saveMany($widgetsToSeed);
}
Related
Let me change my question completely to explain myself better;
I have an order;
An order have multiple order rows. Each order row has two fields; Quantity ordered, and quantity delivered.
If all order rows' quantities delivered are the same as the quantity ordered, the entire order should get a status of '100% delivered'.
If multiple or even one order row's quantities delivered does not match the quantities ordered the entire order should get a status of 'partly delivered'.
If no order row have any deliveries (if all deliveries stands on 0) the status should be '0% delivered'.
What I have so far looks only at the last order row of the entire order because all the previous rows gets overridden by the latest check. This is my code;
public function deliveryAction(Request $request, $id) {
$em = $this->getDoctrine()->getManager();
$order = $em->getRepository('QiBssBaseBundle:PmodOrder')->find($id);
$orderRowsDelivered = $request->request->all();
$delivered = "0%";
foreach ($orderRowsDelivered['order_row_id'] as $orderRowId => $quantityDelivered) {
if($quantityDelivered != '' || $quantityDelivered != null) {
$orderRow = $em->getRepository("QiBssBaseBundle:PmodOrderRow")->find($orderRowId);
$orderDelivered = new PmodDelivery();
$orderDelivered->setOrderRow($orderRow);
$orderDelivered->setQuantity($quantityDelivered);
$orderDelivered->setTimeArrived(new \DateTime());
$em->persist($orderDelivered);
$em->flush();
if($orderRow->getQuantityDelivered() > 0 && $orderRow->getQuantityDelivered() < $orderRow->getQuantity()) {
$delivered = "partly";
} elseif ($orderRow->getQuantityDelivered() == $orderRow->getQuantity()) {
$delivered = "100%";
}
}
}
var_dump($delivered);exit;
return new RedirectResponse ... ;
}
Because as of this moment he looks at the last one with 10 and 8 in the example image, and give a status of 'partly', as soon as the 'quantity delivered' amounts is entered. But he should take all rows together.
I hope this makes more sense.
Based on what Cerad in the comments of his own answer said, this is my answer; (I'll make use of OP's scenario where he uses order rows per order.)
I've added an extra property to my OrderRow entity called $rowStatus.
After that I created a getter function for the $rowStatus called getRowStatus() that gives each row a status individually;
public function getRowStatus()
{
if ($this->getQuantityDelivered() == $this->getQuantity()) {
return $this->rowStatus = 100;
} elseif ($this->getQuantityDelivered() == 0) {
return $this->rowStatus = 0;
} else {
return $this->rowStatus = 50;
}
}
After that in my Order entity I've added a $deliveryStatus property, with a corresponding getter function called getDeliveryStatus() that looks like this;
public function getDeliveryStatus()
{
if (count($this->getOrderRows()) > 0) { //this check is to make sure there are orderRows, because you can't devide by zero if it might happen that there are no order rows. If not the delivery status will just be set on 0.
$sum = 0;
foreach ($this->getOrderRows() as $row) {
$sum += $row->getRowStatus();
}
$average = $sum / count($this->getOrderRows());
if ($average == 100) {
return $this->deliveryStatus = 100;
} elseif ($average == 0) {
return $this->deliveryStatus = 0;
} else {
return $this->deliveryStatus = 50;
}
} else {
return $this->deliveryStatus = 0;
}
}
That's it! After this I just use an enum function to display the 100 as "100% delivered", the 50 as "partly delivered", and the 0 as "0% delivered". I know this isn't really necessary, and you can instead change the status number directly to a string or whatever you want to display.
Just off the top of my head I might do:
$deliveredNone = true;
$deliveredAll = true;
$deliveredSome = false;
foreach ($orderRowsDelivered['order_row_id'] as $orderRowId => $quantityDelivered) {
if ($quantityDelivered) {
$deliveredNone = false; // Know that something has been delivered
}
...
if ($orderRow->getQuantityDelivered() != $orderRow->getQuantity()) {
$deliveredSome = true;
$deliveredAll = false;
}
}
$delivered = null;
if ($deliveredNone) $delivered = '0%';
if ($deliveredAll) $delivered = '100%';
if ($deliveredSome) $delivered = 'partly';
Though I would probably just update the order with the quantities delivered then use a different function to calculate the percentage delivered. As you can see, mixing the two processes can result in confusion.
I made the script to do what is expected, so it work ok but there must be a more elegant way to achieve the same result. I know that using switch will make it look nicer but not sure if the result will be the same as the 'default:' behavior:
This is the section of the script i want to refactor:
foreach ($free_slots as $val) { // here i am looping through some time slots
$slot_out = $free_slots[$x][1];
$slot_in = $free_slots[$x][0];
$slot_hours = $slot_out - $slot_in;
// tasks
if ($slot_out != '00:00:00') {
// Here i call a function that do a mysql query and
// return the user active tasks
$result = tasks($deadline,$user);
$row_task = mysql_fetch_array($result);
// HERE IS THE UGLY PART <<<<<----------------
// the array will return a list of tasks where this current
// users involved, in some cases it may show active tasks
// for other users as the same task may be divided between
// users, like i start the task and you continue it, so for
// the records, user 1 and 2 are involved in the same task.
// The elseif conditions are to extract the info related
// to the current $user so if no condition apply i need
// to change function to return only unnasigned tasks.
// so the i need the first section of the elseif with the
// same conditions of the second section, that is where i
// actually take actions, just to be able to change of
// change of function in case no condition apply and insert
// tasks that are unassigned.
if ($row_task['condition1'] == 1 && etc...) {
} else if ($row_task['condition2'] == 1 && etc...) {
} else if ($row_task['condition3'] == 1 && etc...) {
} else if ($row_task['condition4'] == 1 && etc...) {
} else {
// in case no condition found i change function
// and overwrite the variables
$result = tasks($deadline,'');
$row_task = mysql_fetch_array($result);
}
if ($row_task['condition1'] == 1 && etc...) {
// insert into database
} else if ($row_task['condition2'] == 1 && etc...) {
// insert into database
} else if ($row_task['condition3'] == 1 && etc...) {
// insert into database
} else if ($row_task['condition4'] == 1 && etc...) {
} else {
echo 'nothing to insert</br>';
}
}
}
Basically i run the else if block twice just to be able to change of function in case nothing is found in the first loop and be able to allocate records unassigned.
I haven't changed the functionality of your code, but this is definitely a lot cleaner.
The main problem was that your logic for your if/else statements was confused. When you're writing:
if($a == 1){ } else if($b == 1){ } else if($c == 1){ }else{ //do something }
You're saying If a is 1 do nothing, if b is 1 do nothing, if c is 1 do nothing, but if all of those did nothing, do something when you can just say if a is not 1 and b is not 1 and c is not 1, do something.
I wasn't too sure on your second if statements, but generally it's not good to have an if else with no body within it. However, if the "insert into database" comment does the same thing, you can merge the 3 if statements that do the same code.
I hope i've cleared a few things up for you.
Here's what I ended up with:
foreach ($free_slots as $val) { // here i am looping through some time slots
$slot_out = $free_slots[$x][1];
$slot_in = $free_slots[$x][0];
$slot_hours = $slot_out - $slot_in;
// tasks
if ($slot_out != '00:00:00') {
$result = tasks($deadline, $user);
$row_task = mysql_fetch_array($result);
if (!($row_task['condition1'] == 1 || $row_task['condition2'] == 1 || $row_task['condition3'] == 1 || $row_task['condition4'] == 1)) {
$result = tasks($deadline,'');
$row_task = mysql_fetch_array($result);
}
if ($row_task['condition1'] == 1 && etc...) {
// insert into database
} else if ($row_task['condition2'] == 1) {
// insert into database
} else if ($row_task['condition3'] == 1) {
// insert into database
} else if ($row_task['condition4'] == 1) {
} else {
echo 'nothing to insert</br>';
}
}
}
The php manual states under 'Changelog for break':
5.4.0 Removed the ability to pass in variables (e.g., $num = 2; break $num;) as the numerical argument.
I have a function that copies a table with a tree structure to another table.
After copying each record, the function tests a relation to see if that record has more child records in the next "level" of the tree.
If child records are found, this same function is executed for each child record using a foreach() loop. If they also have child records, the process is repeated, etc. etc.
So the number of "branches" and "levels" in the table will determine how many foreach() loops will be executed. Since the user creates the records, I have no control over the number of "branches" and "levels" in the table.
If break cannot receive a variable any more (I cannot run "branch" and "level" counters any more) - how do you break out of ALL loops if an error occurs?
Scaled down example:
public function copyTreeModels($row, $id)
{
try
{
/* copy current record. */
$status == 'ok';
/* loop to this same function if $status == 'ok' and hasChildren */
if($status == 'ok')
{
If ($row['hasChildren'] == 'yes') // check relation
{
foreach($row['children'] as $child)
{
$this->copyTreeModels($child, $id);
}
}
}
else
{
// break;
throw new CDbException($message);
}
}
catch(CDbException $e)
{
$message .= $e->getMessage();
}
return($message);
}
Don't put try in the recursive function. You need a wrapper function around the whole thing that establishes the condition handler:
public function copyTreeModels($row, $id) {
try {
$this->copytreeModelsRecurse($row, $id);
}
catch(CDbException $e)
{
$message .= $e->getMessage();
}
return($message);
}
copyTreeModelsRecurse would then be your copyTreeModels function, but without the try/catch blocks.
The only thing I can think of is to set a variable which determines whether or not to break all.
An example (untested) is
$break_all = false;
foreach($values as $val) {
foreach($val as $val1) {
foreach($val1 as $val2) {
if($val2 == "xyz") {
$break_all = true;
}
if($break_all) break;
}
if($break_all) break;
}
if($break_all) break;
}
I'll agree with the fact it's not a pretty solution however.
Edit:
An alternative suggestion if the amount of nested loops to break; from is a variable amount is setting a counter and decrementing the break counter for each break, and only breaking if it is greater than 0:
$break_count = 0;
foreach($values as $val) {
foreach($val as $val1) {
foreach($val1 as $val2) {
if($val2 == "xyz") {
$break_count = 3;
}
if($break_count > 0) { $break_count--; break; }
}
if($break_count > 0) { $break_count--; break; }
}
if($break_count > 0) { $break_count--; break; }
}
I have a MySQL table holding lots of records that i want to give the user access to. I don't want to dump the entire table to the page so i need to break it up into 25 records at a time, so i need a page index. You have probably seen these on other pages, they kind of look like this at the base of the page:
< 1 2 3 4 5 6 7 8 9 >
For example, when the user clicks on the '4' link, the page refreshes and the offset is moved on (4th page x 25 records). Here is what i already have:
function CreatePageIndex($ItemsPerPage, $TotalNumberOfItems, $CurrentOffset, $URL, $URLArguments = array())
{
foreach($URLArguments as $Key => $Value)
{
if($FirstIndexDone == false)
{
$URL .= sprintf("?%s=%s", $Key, $Value);
$FirstIndexDone = true;
}
else
{
$URL .= sprintf("&%s=%s", $Key, $Value);
}
}
Print("<div id=\"ResultsNavigation\">");
Print("Page: ");
Print("<span class=\"Links\">");
$NumberOfPages = ceil($TotalNumberOfItems / $ItemsPerPage);
for($x = 0; $x < $NumberOfPages; $x++)
{
if($x == $CurrentOffset / $ItemsPerPage)
{
Print("<span class=\"Selected\">".($x + 1)." </span>");
}
else
{
if(empty($URLArguments))
{
Print("".($x + 1)." ");
}
else
{
Print("".($x + 1)." ");
}
}
}
Print("</span>");
Print(" (".$TotalNumberOfItems." results)");
Print("</div>");
}
Obviously this piece of code does not create a dynamic index, it just dumps the whole index at the bottom of the page for every page available. What i need is a dynamic solution that only shows the previous 5 pages and next 5 pages (if they exist) along with a >> or something to move ahead 5 or so pages.
Anybody seen an elegant and reusable way of implementing this as i feel i'm re-inventing the wheel? Any help is appreciated.
Zend Framework is becoming a useful collection and includes a Zend_Paginator class, which might be worth a look. Bit of a learning curve and might only be worth it if you want to invest the time in using other classes from the framework.
It's not too hard to roll your own though. Get a total count of records with a COUNT(*) query, then obtain a page of results with a LIMIT clause.
For example, if you want 20 items per page, page 1 would have LIMIT 0,20 while page 2 would be LIMIT 20,20, for example
$count=getTotalItemCount();
$pagesize=20;
$totalpages=ceil($count/$pagesize);
$currentpage=isset($_GET['pg'])?intval($_GET['pg']):1;
$currentpage=min(max($currentpage, 1),$totalpages);
$offset=($currentpage-1)*$pagesize;
$limit="LIMIT $offset,$pagesize";
It's called Pagination:
a few examples:
A nice one without SQL
A long tutorial
Another tutorial
And Another
And of course.. google
How about this jQuery-plugin?
So all the work is done on the clientside.
http://plugins.jquery.com/project/pagination
demo: http://d-scribe.de/webtools/jquery-pagination/demo/demo_options.htm
Heres an old class I dug out that I used to use in PHP. Now I handle most of it in Javascript. The object takes an array (that you are using to split the stack into pages) and return the current view. This can become tedious on giant tables so keep that in mind. I generally use it for paging through small data sets of under 1000 items. It can also optionally generate your jump menu for you.
class pagination {
function pageTotal($resultCount, $splitCount) {
if (is_numeric($resultCount) && is_numeric($splitCount)) {
if ($resultCount > $splitCount) {
$pageAverage = (integer)$resultCount / $splitCount;
$pageTotal = ceil($pageAverage);
return $pageTotal;
} else {
return 1;
}
} else {
return false;
}
}
function pageTotalFromStack($resultArray, $splitCount) {
if (is_numeric($splitCount) && is_array($resultStack)) {
if (count($resultStack) > $splitCount) {
$resultCount = count($resultStack);
$pageAverage = (integer)$resultCount / $splitCount;
$pageTotal = ceil($pageAverage);
return $pageTotal;
} else {
return 1;
}
} else {
return false;
}
}
function makePaginationURL($preURL, $pageTotal, $selected=0, $linkAttr=0, $selectedAttr=0) {
if (!empty($preURL) && $pageTotal >= 1) {
$pageSeed = 1;
$passFlag = 0;
$regLink = '<a href="{url}&p={page}"';
if (is_array($linkAttr)) $regLink .= $this->setAttributes($linkAttr); //set attributes
$regLink .= '>{page}</a>';
$selLink = '<a href="{url}&p={page}"';
if (is_array($selectedAttr)) $selLink .= $this->setAttributes($selectedAttr); //set attributes
$selLink .= '>{page}</a>';
while($pageSeed <= $pageTotal) {
if ($pageSeed == $selected) {
$newPageLink = str_replace('{url}', $preURL, $selLink);
$newPageLink = str_replace('{page}', $pageSeed, $newPageLink);
} else {
$newPageLink = str_replace('{url}', $preURL, $regLink);
$newPageLink = str_replace('{page}', $pageSeed, $newPageLink);
}
if ($passFlag == 0) {
$passFlag = 1;
$linkStack = $newPageLink;
} else {
$linkStack .= ', ' . $newPageLink;
}
$pageSeed++;
}
return $linkStack;
} else {
return false;
}
}
function splitPageArrayStack($stackArray, $chunkSize) {
if (is_array($stackArray) && is_numeric($chunkSize)) {
return $multiArray = array_chunk($stackArray, $chunkSize);
} else {
return false;
}
}
}
I'm working with arrays of image filepaths. A typical array might have 5 image filepaths stored in it.
For each array, I want to pull out just the "best" photo to display as a thumbnail for the collection.
I find looping and arrays very confusing and after 4 hours of trying to figure out how to structure this, I'm at a loss.
Here are the rules I'm working with:
The very best photos have "-large" in their filepaths. Not all arrays will have images like this in them, but if they do, that's always the photo I want to pluck out.
The next best photos are 260px wide. I can look this up with getimagesize. If I find one of these, I want to stop looking and use it.
The next best photos are 265 wide. If I find one I want to use it and stop looking.
The next best photos are 600px wide. Same deal.
Then 220px wide.
Do I need 5 separate for loops? 5 nested for-loops
Here's what I'm trying:
if $image_array{
loop through $image_array looking for "-large"
if you find it, print it and break;
if you didn't find it, loop through $image_array looking for 260px wide.
if you find it, print it and break;
}
and so on....
But this doesn't appear to be working.
I want to "search" my array for the best single image based on these criteria. If it can't find the first type, then it looks for the second on so on. How's that done?
// predefined list of image qualities (higher number = best quality)
// you can add more levels as you see fit
$quality_levels = array(
260 => 4,
265 => 3,
600 => 2,
220 => 1
);
if ($image_arry) {
$best_image = null;
// first search for "-large" in filename
// because looping through array of strings is faster then getimagesize
foreach ($image_arry as $filename) {
if (strpos('-large', $filename) !== false) {
$best_image = $filename;
break;
}
}
// only do this loop if -large image doesn't exist
if ($best_image == null) {
$best_quality_so_far = 0;
foreach ($image_arry as $filename) {
$size = getimagesize($filename);
$width = $size[0];
// translate width into quality level
$quality = $quality_levels[$width];
if ($quality > $best_quality_so_far) {
$best_quality_so_far = $quality;
$best_image = $filename;
}
}
}
// we should have best image now
if ($best == null) {
echo "no image found";
} else {
echo "best image is $best";
}
}
Another approach (trivial, less generic, slower). Just check rules one by one:
function getBestFile($files) {
foreach ($files as $arrayKey => $file) {
if (strstr($file, '-large') !== FALSE) {
return $file;
}
}
foreach ($files as $arrayKey => $file) {
if (is260wide($file)) {
return $file;
}
}
// ...
}
You need 3 loops and a default selection.
loop through $image_array looking for "-large"
if you find it, return it;
if you didn't find it, loop through $image_array
get image width
if prefered width (260px), return it.
if $sizes[$width] not set, add filename
loop a list of prefered sizes in order and see if it is set in $sizes
if you find it, return it;
return the first image or default image;
<?php
// decide if 1 or 2 is better
function selectBestImage($image1, $image2) {
// fix for strange array_filter behaviour
if ($image1 === 0)
return $image2;
list($path1, $info1) = $image1;
list($path2, $info2) = $image2;
$width1 = $info1[0];
$width2 = $info2[0];
// ugly if-block :(
if ($width1 == 260) {
return $image1;
} elseif ($width2 == 260) {
return $image2;
} elseif ($width1 == 265) {
return $image1;
} elseif ($width2 == 265) {
return $image2;
} elseif ($width1 == 600) {
return $image1;
} elseif ($width2 == 600) {
return $image2;
} elseif ($width1 == 220) {
return $image1;
} elseif ($width2 == 220) {
return $image2;
} else {
// nothing applied, so both are suboptimal
// just return one of them
return $image1;
}
}
function getBestImage($images) {
// step 1: is the absolutley best solution present?
foreach ($images as $key => $image) {
if (strpos($image, '-large') !== false) {
// yes! take it and ignore the rest.
return $image;
}
}
// step 2: no best solution
// prepare image widths so we don't have to get them more than once
foreach ($images as $key => $image) {
$images[$key] = array($image, getImageInfo($image));
}
// step 3: filter based on width
$bestImage = array_reduce($images, 'selectBestImage');
// the [0] index is because we have an array of 2-index arrays - ($path, $info)
return $bestImage[0];
}
$images = array('image1.png', 'image-large.png', 'image-foo.png', ...);
$bestImage = getBestImage($images);
?>
this should work (i didn't test it), but it is suboptimal.
how does it work? first, we look for the absolutely best result, in this case, -large, because looking for a substrings is inexpensive (in comparsion).
if we don't find a -large image we have to analyze the image widths (more expensive! - so we pre-calculate them).
array_reduce calls a filtering function that takes 2 array values and replaces those two by the one return by the function (the better one). this is repeated until there is only one value left in the array.
this solution is still suboptimal, because comparisons (even if they're cheap) are done more than once. my big-O() notation skills are a bit (ha!) rusty, but i think it's O(n*logn). soulmerges solution is the better one - O(n) :)
you could still improve soulmerges solution, because the second loop is not necessary:
first, pack it into a function so you have return as a break-replacement. if the first strstr matches, return the value and ignore the rest. afterwards, you don't have to store the score for every array key. just compare to the highestKey variable and if the new value is higher, store it.
<?php
function getBestImage($images) {
$highestScore = 0;
$highestPath = '';
foreach ($images as $image) {
if (strpos($image, '-large') !== false) {
return $image;
} else {
list($width) = getImageInfo($image);
if ($width == 260 && $highestScore < 5) {
$highestScore = 5;
$highestPath = $image;
} elseif ($width == 265 && $highestScore < 4) {
$highestScore = 4;
$highestPath = $image;
} elseif ($width == 600 && $highestScore < 3) {
$highestScore = 3;
$highestPath = $image;
} elseif ($width == 220 && $highestScore < 2) {
$highestScore = 2;
$highestPath = $image;
} elseif ($highestScore < 1) {
// the loser case
$highestScore = 1;
$highestPath = $image;
}
}
}
return $highestPath;
}
$bestImage = getBestImage($images);
?>
didn't test, should work in O(n). can't imagine a faster, more efficient way atm.
I would assign points to the files depending on how many rules apply. If you want certain rules to supercede others, you can give more points for that rule.
define('RULE_POINTS_LARGE', 10);
define('RULE_POINTS_260_WIDE', 5);
// ...
$points = array();
foreach ($files as $arrayKey => $file) {
$points[$arrayKey] = 0;
if (strstr($filename, '-large') !== FALSE) {
$points[$arrayKey] += RULE_POINTS_LARGE;
}
// if ...
}
// find the highest value in the array:
$highestKey = 0;
$highestPoints = 0;
foreach ($points as $arrayKey => $points) {
if ($files[$arrayKey] > $highestPoints) {
$highestPoints = $files[$arrayKey];
$highestKey = $arrayKey;
}
}
// The best picture is $files[$highestKey]
One more side note: Givign your rules multiples of a value will ensure that a rule can be 'stronger' than all others. Example: 5 rules -> rule values (1, 2, 4, 8, 16).
1 < 2
1 + 2 < 4
1 + 2 + 4 < 8
etc.