Yii2 Rate Limiting Api - yii2

I'm worried about Yii2 Rate limiting api?
What is Rate limiting api, why this used?
Here are some methods from Yii2
Can a yii guru explain in simple words about these methods, where and when I should use rate limiting in my api?
public function getRateLimit($request, $action)
{
return [$this->rateLimit, 1]; // $rateLimit requests per second
}
public function loadAllowance($request, $action)
{
return [$this->allowance, $this->allowance_updated_at];
}
public function saveAllowance($request, $action, $allowance, $timestamp)
{
$this->allowance = $allowance;
$this->allowance_updated_at = $timestamp;
$this->save();
}

THE METHODS
getRateLimit(), loadAllowance() and saveAllowance() are three methods contained in the \yii\filters\RateLimitInterface Inteface that the user identity class should implement for enable rate limiting of your api.
getRateLimit() is the first method and it returns the maximum number of api calls that you can do in x seconds:
public function getRateLimit($request, $action) {
return [1,20]; // There can be 1 api call every 20 seconds
}
loadAllowance() return the number of the remaining allowed requests with the corresponding UNIX timestamp of the last time these where checked.
public function loadAllowance($request, $action)
{
return [$this->allowance, $this->allowance_updated_at];
}
saveAllowance() assign to $this->allowance the value of remaining allowed requests and save the timestamp in $this->allowance_updated_at.
public function saveAllowance($request, $action, $allowance, $timestamp)
{
$this->allowance = $allowance; //Saving Remaining Requests
$this->allowance_updated_at = $timestamp; // Saving Timestamp
$this->save(); //Save the model
}
IMPLEMENTATION
This is how implemented the Rate Limiting in my example application (using advanced template):
1 Set the user identity class
In the config/main.php of your api application set the user component.
'user' => [
'identityClass' => 'api\models\User', // User Model for your api
'enableSession' => false,
'loginUrl' => null,
],
2 Create a user model
This is model should implement the \yii\filters\RateLimitInterface:
This is mine:
class User extends \common\models\User implements \yii\filters\RateLimitInterface
{
public $rateLimit = 1;
public $allowance;
public $allowance_updated_at;
public function getRateLimit($request, $action) {
return [$this->rateLimit,1];
}
public function loadAllowance($request, $action)
{
return [$this->allowance, $this->allowance_updated_at];
}
public function saveAllowance($request, $action, $allowance, $timestamp)
{
$this->allowance = $allowance;
$this->allowance_updated_at = $timestamp;
$this->save();
}
}
After these two step Yii will automatically use yii\filters\RateLimiter configured as an action filter for yii\rest\Controller to perform rate limiting check (as cited in the documentation).
The last thing you have to do is disable the Rate limit header in your rest controller behaviors:
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['rateLimiter']['enableRateLimitHeaders'] = false;
return $behaviors;
}
WHEN YOU SHOULD USE RATE LIMITING IN YOUR APPLICATION
Api calls return data (with your filters) from your database so when they're called the server execute queries. More are the calls more are also the number of queries that are execute,
You must limit the number of the calls in order to prevent Server heavy works and a resulting fallout of your system.
Hope this will help.
I'm not going far from the Yii2 Guide, but i don't think i can explain this in a simplier way.

Maybe Yii2 document could help you a lot,and link following,
http://www.yiiframework.com/doc-2.0/guide-rest-rate-limiting.html
you need to alter your user table in Database.The rate limiting takes effect for user loginned

I implemented each step but not show the headers
X-Rate-Limit-Limit, the maximum number of requests allowed with a time period
X-Rate-Limit-Remaining, the number of remaining requests in the current time period
X-Rate-Limit-Reset, the number of seconds to wait in order to get the maximum number of allowed requests
`

you can use rate limithing composer.
add :"ethercreative/yii2-ip-ratelimiter": "1.*"
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['rateLimiter'] = [
// Use class
'class' => RateLimiter::className(),
'rateLimit' => 1,
'timePeriod' => 2,
'separateRates' => false,
'enableRateLimitHeaders' => false,
];
return $behaviors;
}

public function getRateLimit($request, $action) {
$id = $action->getUniqueId();
$limits = [
'user/login' => [20,10],
'article/index' => [100,10],
.......
'other' => [50,10]
];
if(!array_key_existe($id,$limits)) $id = 'other';
return $limits[$id];
}

Related

How use retryable jobs in yii 2 queue AMQP Interop (rabbitmq)?

How use retryable jobs in yii 2 queue AMQP Interop (rabbitmq) ?
It is in the documentation.
You can configure "queue" component for your application
'queue' => [
'class' => \yii\queue\<driver>\Queue::class,
'ttr' => 5 * 60, // Max time for job execution
'attempts' => 3, // Max number of attempts
],
Also, you can override config for a specific Job Model
class SomeJob extends BaseObject implements RetryableJobInterface {
public function execute($queue) {
//...
}
public function getTtr() {
return 15 * 60;
}
public function canRetry($attempt, $error) {
return ($attempt < 5) && ($error instanceof TemporaryException);
}
}
To consume a queue you need worker(s).
Simply you can start a worker with
./yii queue/listen
command

How to improve performance of multiple count queries in a laravel view

I'm working on a marketing application that allows users to message their contacts. When a message is sent, a new "processed_message" database entry is created. There is a list view that displays all campaigns and the number of messages sent, blocked and failed for each campaign. My problem is that this list view takes way too long to load after there are > 50 campaigns with lots of messages.
Currently each campaign has 3 computed attributes (messages_sent, messages_failed and messages_blocked) that are all in the Campaign model's "appends" array. Each attribute queries the count of processed_messages of the given type for the given campaign.
namespace App;
class Campaign
{
protected $appends = [
'messages_sent',
'messages_blocked',
'messages_failed'
];
/**
* #relationship
*/
public function processed_messages()
{
return $this->hasMany(ProcessedMessage::class);
}
public function getMessagesSentAttribute()
{
return $this->processed_messages()->where('status', 'sent')->count();
}
public function getMessagesFailedAttribute()
{
return $this->processed_messages()->where('status', 'failed')->count();
}
public function getMessagesBlockedAttribute()
{
return $this->processed_messages()->where('status', 'blocked')->count();
}
}
I also tried to query all of the messages at once or in chunks to reduce the number of queries but getting all of the processed_messages for a campaing at once will overflow memory and the chunking method is way too slow since it has to use offset. I considered using eager loading the campaigns with processed_messages but that would obviously use way too much memory as well.
namespace App\Http\Controllers;
class CampaignController extends Controller
{
public function index()
{
$start = now();
$campaigns = Campaign::where('user_id', Auth::user()->id)->orderBy('updated_at', 'desc')->get();
$ids = $campaigns->map(function($camp) {
return $camp->id;
});
$statistics = ProcessedMessage::whereIn('campaign_id', $ids)->select(['campaign_id', 'status'])->get();
foreach($statistics->groupBy('campaign_id') as $group) {
foreach($group->groupBy('status') as $messages) {
$status = $messages->first()->status;
$attr = "messages_$status";
$campaign = $campaigns->firstWhere('id', $messages->first()->campaign_id);
$campaign->getStatistics()->$attr = $status;
}
}
return view('campaign.index', [
'campaigns' => $campaigns
]);
}
}
My main goal is to reduce the current page load time considerably (which can take anywhere from 30 seconds to 5 minutes when there are a bunch of campaigns).
You could use the withCount method to count all the objects without loading the relation.
Reference:
If you want to count the number of results from a relationship without actually loading them you may use the withCount method, which will place a {relation}_count column on your resulting models.
In your controller you could do this:
$count = Campaign::withCount(['processed_messages' => function ($query) {
$query->where('content', 'sent');
}])->get();
You could do multiple counts in the same relationship too:
$campaigns = Campaign::withCount([
'processed_messages',
'processed_messages as sent_message_count' => function ($query) {
$query->where('content', 'sent');
}],
'processed_messages as failed_message_count' => function ($query) {
$query->where('status', 'failed');
}],
'processed_messages as blocked_message_count' => function ($query) {
$query->where('status', 'blocked');
}])->get();
You can access the count with this:
echo $campaigns[0]->sent_message_count
Docs

count(): Parameter must be an array or an object that implements Countable in php 7.2.1

count(): Parameter must be an array or an object that implements Countable in php 7.2.1
using Yii2
How to resolve this issue?
public static function findAdminByUsername($username)
{
$adminUser = static::find()->where(['username' => $username,'userType'=>'ADMIN','user_status'=>self::STATUS_ACTIVE])->one();
if(count($adminUser)>0){
return $adminUser;
}else{
return null;
}
}
The thing is you are checking for count > 1 with using ->one() which looks odd, looking at your code you want to return NULL if there is no record found and if you look into documentation the function one() already returns NULL if there are no records found so you are adding extra code and it could easily be reduced to
public static function findAdminByUsername($username)
{
return static::find()->where(
[
'username' => $username,
'userType' => 'ADMIN',
'user_status' => self::STATUS_ACTIVE,
]
)->one();
}
You are using find()......->one()
so your query should return just an object .. without iteration capabilities.
if you want check if the find() return a value or not then you could check with isset. find()->one() return null if the query fail.
public static function findAdminByUsername($username)
{
$adminUser = static::find()->where(['username' => $username,'userType'=>'ADMIN','user_status'=>self::STATUS_ACTIVE])->one();
if( $adminUser !== null ){
return $adminUser;
}else{
return null;
}
}
if you don't need others that return the result for find()->..one() you could simply return
return static::find()->
where(['username' => $username,'userType'=>'ADMIN','user_status'=>self::STATUS_ACTIVE])
->one();

How come ArrayDataProvider is best used for big array but is less efficient yii2

I have a class which extends from yii\rest\Controller, under this class is a function which returns an instance of ArrayDataProvider in JSON format.
public function actionIndex()
{
$user = User::find();
return $this->query($user->asArray()->all(), true);
}
protected function query($qry, $paginate=false)
{
$this->serializer['preserveKeys'] = false;
$dataProvider = new ArrayDataProvider([
'allModels' => $qry,
]);
if ($paginate) {
$dataProvider->setPagination(['pageSize' => 20]);
} else {
$dataProvider->setPagination(false);
}
return $dataProvider;
}
Assuming that the array returned is composed of 130k items (from actionIndex). Do everytime I hit this API with a page parameter on it, the ArrayDataProvider will process the 130k records and sliced it all over again?
Can someone tell what's the exact behavior an ArrayDataProvider is running? Is there any efficient way of handling such big array? What's the perks of using this as opposed with other providers?
Thank you!

Yii2 Model Rules Update Checking Rules For Same Record

I.E. If i update address_line1 then its giving error for mobile number allocated.
While update it should not match with it self.
Even if i change mobile number it should check with other user.
public function rules()
{
return [
[['mobile_number','address_line1','address_line2','city','state','country','pincode' ],'required'],
['mobile_number','mobile_number_allocation_validate'],
];
}
public function mobile_number_allocation_validate($attribute){
// add custom validation
$result = User::find()
->where('`mobile_number` = "'.$this->$attribute.'" AND
`status` = "A" ')->all();
if(!empty($result)){
$this->addError($attribute,'Mobile number is allocated to other vehicle');
}
}
Thanks in Advance
Change your condition as :-- Override beforeSave() of User ActiveRecord
/**
* BeforeSave() check if User ActiveRecord is created with same calculator param
* if created then return false with model error
*
*/
public function beforeSave($insert){
$model = self::find()->where('`mobile_number` = "'.$this->$attribute.'" AND
`status` = "A" ')->all();
if($model !== null && $model->id !== $this->id){
$this->addError('mobile_number' , 'Mobile number is allocated to other vehicle');
return false;
}
return parent::beforeSave($insert);
}
You should be able to use the unique validator for this, by simply adding a filter condition like this
public function rules()
{
return [
[['mobile_number','address_line1','address_line2','city','state','country','pincode' ],'required'],
['mobile_number','unique', 'filter' => ['status' => 'A']],
];
}