Yii1 - CDataProviderIterator limit doesn't work - yii

I am using criteria and CActiveDataProvider to make the query, and then I want to use CDataProviderIterator, but it seems that CDataProviderIterator is ignoring the limit.
The code so far:
$criteria = new CDbCriteria();
$criteria->select = "t.id";
$criteria->join = 'LEFT JOIN purged_files ON t.id = purged_files.project_id';
$criteria->order = "t.id asc";
$criteria->condition = " purged_files.id IS NULL AND `new_status_id` IN ('DELIVERED', 'PAID') AND `created` <= DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 180 DAY)";
$criteria->limit = 1;
$criteria->offset = 0;
$criteria->together = true;
$dataProvider = new CActiveDataProvider('Project', array(
'criteria' => $criteria,
'pagination' => false
));
$iterator = new CDataProviderIterator($dataProvider);
What seems to be wrong?

You cannot disable pagination and use CDataProviderIterator at the same time. If you look into source of CDataProviderIterator you will see that it always use pagination. That is purpose of this class - it uses pagination to avoid loading millions of records into memory at the same time. If your limit is low and loading all data into memory will not exceed some memory limits, you probably don't need to use CDataProviderIterator at all - you can fetch data directly from data provider:
foreach ($dataProvider->getData() as $project) {
$project->doSomething();
}
Or do not use data provider at all:
$models = Project::model()->findAll($criteria);
If your really need a CDataProviderIterator, you may set limit by using totalItemsCount property to override real number of records:
$dataProvider = new CActiveDataProvider('Project', array(
'criteria' => $criteria,
'totalItemsCount' => 5000,
));
Make sure that your totalItemsCount is not lower than actual number of records. You may want to query database to calculate this value.

Related

Laravel Row duplication inserted, with updateOrCreate method with Race-Condition

i have function in my controller that create a forecast :
public function updateOrCreate(Request $request, $subdomain, $uuid)
{
$fixture = Fixture::where('uuid',$uuid)->firstOrFail();
request()->validate([
'local_team_score' => 'integer|min:0',
'visitor_team_score' => 'integer|min:0',
'winner_team_id' => 'integer|nullable'
]);
if ($fixture->status !== "PENDING"){
return response()->json([
'message' => "You can not add or modify a forecast if the fixture is not pending"
], 403);
}
$winner_team = null;
// local team win
if ($request->local_team_score > $request->visitor_team_score) {
$winner_team = $fixture->localTeam;
}elseif ($request->local_team_score < $request->visitor_team_score){ //visitor win
$winner_team = $fixture->visitorTeam;
}else{ // draw
$winner_team = FixtureTeam::where('team_id',$request->winner_team_id)->first();
}
$user = auth('api')->user();
$platform = Platform::first();
$forecast = Forecast::updateOrCreate([
'user_id' => $user->id,
'fixture_id' => $fixture->id,
'platform_id' => $platform->id
],[
'local_team_score' => $request->local_team_score,
'visitor_team_score' => $request->visitor_team_score,
'winner_team_id' => is_null($winner_team) ? null : $winner_team->team_id
]);
$forecast->load('winnerTeam');
return new ForecastResource($forecast);
}
As you can see i use updateOrCreate methods to add or update a forecast.
The problem is when 2 requests from the same user run at the same time (and no forecast is already created) 2 row are inserted.
Do you have a solution ?
I See that the problem is not new but i could not find a solution https://github.com/laravel/framework/issues/19372
updateOrCreate does 2 steps:
tries to fetch the record
depending on the outcome does an update or a create.
This operation is not atomic, meaning that between step 1 and 2 another process could create the record and you would end up with duplicates (your situation).
To solve your problem you need following:
determine what columns would give the uniqueness of the record and add an unique index (probably compound between user_id, fixture_id, platform_id)
you need to let database handle the upsert (ON DUPLICATE KEY UPDATE in MySQL, ON CONFLICT (...) DO UPDATE SET in Postgres, etc). This can be achieved in Laravel by using the upsert(array $values, $uniqueBy, $update = null) instead of updateOrCreate.

Bigcommerce API Tracking Number Create PHP

I am trying to update the tracking number of an order in Bigcommerce using the API. This is the code I am using:
//update BC of order status
$filter = array('status_id' => 2);
$order_status_update = BigCommerce::updateResource('/orders/' . 105, $filter);
$order = Bigcommerce::getOrder(105);
foreach($order->products as $shipment)
{
$filter = array(
'order_address_id' => $shipment->order_address_id,
'items'=> array(
'order_product_id' => $shipment->product_id,
'quantity' => $shipment->quantity
),
'tracking_number' => 'tracking number'
);
$add_tracking = BigCommerce::createResource('/orders/105/shipments', $filter);
var_dump($add_tracking);
}
I have followed the instructions from here:
https://developer.bigcommerce.com/api/stores/v2/orders/shipments#list-shipments
BigCommerce Uploading Tracking Numbers
But I can't seem to get it to work. Can someone help?
In advance, thanks for your help!
Akshay
The payload for creating a shipment is invalid due to the items field needing to be formatted as an object array and the use of the product_id as opposed to the ID of the product within the order.
The code provided is attempting to create one shipment per product in the order, is this intended? Ideally you would ship all items in one shipment, which is why the items field is meant to be an array of product objects.
Additionally, by creating a shipment for an order the order status will automatically change to "Shipped", so the first PUT request is unnecessary.
If you were trying to create a single shipment per order and are assuming no orders will have multiple addresses then this code should work.
$order = Bigcommerce::getOrder(105);
$filter = array(
'order_address_id' => $order->shipping_addresses[0]->id,
'tracking_number' => '123456'
);
foreach($order->products as $product) {
$items[] = array(
'order_product_id' => $product->id,
'quantity' => $product->quantity
);
}
$filter['items'] => $items;
$add_tracking = BigCommerce::createResource('/orders/105/shipments', $filter);
var_dump($add_tracking);

Problems returning result of CDbCriteria based query

I have a query as follows
$criteria1 = new CDbCriteria();
$criteria1->condition = 'id = 1';
$modelA=Table1::model()->find($criteria1);
I can pass it to a view and return the title and entry
$this->widget('bootstrap.widgets.TbBox', array(
title' => $modelA['title'],
'content' => $modelA['entry'] ));
Now I'd like to return a range of entries
$criteria2 = new CDbCriteria();
$criteria2->condition = 'id > 7';
$modelB=Table1::model()->findAll($criteria2);
(btw : I'm following a form as laid out here). I was expecting to be able to read the resulting array of values out as below, but ['title'] is now being seen as a undefined index (obviously I'm expecting to read this out in a loop but you get the point)
$this->widget('bootstrap.widgets.TbBox', array(
'title' => $modelB['title'][0],
'content' => $modelB['entry'][0]));
Where am I going wrong?
Thanks
No, the indexes should be specified in the different order: the number of a specific element first, then the name of the property. Additionally, it's better (=cleaner) to name the result of findAll so it'll show you (and any other reader) that it's a collection, not a single model:
$models = Table1::model()->findAll($criteria2);
// ...
$this->widget('bootstrap.widgets.TbBox', array(
'title' => $models[0]['title']
//...
));
But even that's not necessary if you use foreach (and you probably will):
foreach ($models as $model):
// ...
$this->widget('some.name', array(
'title' => $model['title']
);
endforeach;

Dataprovider to EExcelView

lets say i have a table named
cars{
'id','name','brand_id',
}
and another table
brand{
'id','brand_name',
}
I have a situation that i want to generate an Excel report with the following attributes.
'name','brand_name' i.e. SELECT cars.name, brand.brand_name FROM cars INNER JOIN on brand WHERE cars.brand_id = brand.id
So i created a dataprovider like this:
$sql = "SELECT cars.name, brand.brand_name FROM cars INNER JOIN brand on cars.brand_id = brand.id";
$result = Yii::app()->db->createCommand($sql)->queryAll();
$this->render('doc', array('dataprovider' => $result));
Now i want to generate Excel file with result as a dataProvider so i write the following code:
// lets say i am doing this in view page named doc.php
$factory = new CWidgetFactory();
Yii::import('ext.eexcelview.EExcelView',true);
$widget = $factory->createWidget($this,'EExcelView', array(
'dataProvider'=>$dataprovider->search(),
'grid_mode'=>'export',
'title'=>'Title',
'creator'=>'TNC',
'autoWidth'=>false,
'filename'=>'Report.xlsx',
'stream'=>false,
'disablePaging'=>false,
'exportType'=>'Excel2007',
'columns'=>array(
'name',
'brand_name',),
'showTableOnEmpty' => false,
));
$widget->init();
$widget->run();
I have included all the extensions that i have to.. This code is working when i fed the dataProvider field with a single table entry .
But the situation arises when i include multiple tables.
These lines don't actually make a dataprovider:
$result = Yii::app()->db->createCommand($sql)->queryAll();
$this->render('doc', array('dataprovider' => $result));
You'll want to do something like the following:
$dataprovider = new CSqlDataProvider($sql, array(
'pagination'=>false,
);
$this->render('doc', array('dataprovider' => $dataprover);
More info here: http://www.yiiframework.com/doc/api/1.1/CSqlDataProvider
This works for 2 tables, dont know works for more than 2 or not.
$dataProvider = new CArrayDataProvider($dataprovider, array('id' => 'brand', 'sort' => array('attributes' => array('brand_name', ), ), 'pagination' => false));
$this -> render('doc', array('dataprovider' => $dataProvider,));

limit number of results using magento API V2

Hi I have searched the site for my question but haven't found an easy solution and I think the issue is so basic.
I'm using Api V2 so maybe there's a solution now. Here I go, this is my code:
$api_soap_url = 'http://localhost/magento/api/v2_soap?wsdl=1';
$client = new SoapClient($api_soap_url);
$session_id = $client->__soapCall('login',array($user, $pw));
$data = array($session_id);
$result = $client->__soapCall('customerCustomerList', $data);
This returns all results, I need to limit number of result so I have tried using filters and other solutions found here but no luck.
The only one I haven't tried is this one:
Control the number of results from a Magento API call
But filtering by date doesn't solve my problem and rewriting classes is a ver complex solution for such a simple need.
Thanks in advance
I'm not sure the filter can limit number of result but you can try this:
$complexFilter = array(
'complex_filter' => array(
array(
'key' => 'created_at',
'value' => array('key' => 'gt', 'value' => '2012-05-13 06:11:00')
// where created_at is greater than 2012-05-13 06:11:00
// For example: eq (equals), neq (not equals), gt (greater than), lt (less than), etc.
)
)
);
$result = $client->customerCustomerList($session, $complexFilter);
I ended up overriding app/code/core/Mage/Sales/Model/Order/Api.php, adding a "special magic" field called "collection.limit". Your mileage may vary; I have tight controls on both the Magento installation and the programs (in this case, a set of C# programs) accessing the Magento installation.
My caller simply uses the "magic field" as a key/ value pair, something like this (please test, again, I was calling from C#, so this php should be considered suspect):
$collectionLimitClause = array (
'key' => 'collection.limit',
'value' => array('key' => 'eq', 'value' => '10')
);
In my Magento installation (this part is tested, live and running), I created a Sales/Model/Order/Api.php in my local namespace and over-rode the items function. Around the 32nd or so line of that function, you'll see this:
$apiHelper = Mage::helper('api');
$filters = $apiHelper->parseFilters($filters, $this->_attributesMap['order']);
try {
foreach ($filters as $field => $value) {
$orderCollection->addFieldToFilter($field, $value);
}
} catch (Mage_Core_Exception $e) {
$this->_fault('filters_invalid', $e->getMessage());
}
Instead, I "catch" my own magic limiter with the strncmp here, with an if-else inside the foreach:
$apiHelper = Mage::helper('api');
$filters = $apiHelper->parseFilters($filters, $this->_attributesMap['order']);
try {
foreach ($filters as $field => $value) {
if( !strncmp($field,"collection.limit",16) ) {
$orderCollection->getSelect()->limit($value['eq']);
}
else {
$orderCollection->addFieldToFilter($field, $value);
}
}
} catch (Mage_Core_Exception $e) {
$this->_fault('filters_invalid', $e->getMessage());
}
I'm not overly excited by this, but, I think it's pretty safe and it works.