-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Dmitry Demin
committed
Jun 21, 2016
1 parent
9bcffd3
commit 272e3ed
Showing
3 changed files
with
295 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
<?php | ||
|
||
namespace sizeg\newtoncoolranking; | ||
|
||
/** | ||
* Class NewtonCoolRankingBehavior | ||
* | ||
* Rank Hotness With Newton's Law of Cooling | ||
* | ||
* @author Dmitry Demin <[email protected]> | ||
*/ | ||
class NewtonCoolRankingBehavior extends \yii\base\Behavior | ||
{ | ||
|
||
/** | ||
* @var string the attribute that will receive current temperature value | ||
*/ | ||
public $rankAttribute = 'rank'; | ||
|
||
/** | ||
* @var string the attribute that will receive time value of last temperature update | ||
*/ | ||
public $rankTimeAttribute = 'rankTime'; | ||
|
||
/** | ||
* @var string the attribute that store temperature boost value | ||
*/ | ||
public $rankBoostAttribute = 'rankBoost'; | ||
|
||
/** | ||
* @var int Initial temperature for the new item reflecting its hotness | ||
*/ | ||
public $initial = 1000; | ||
|
||
/** | ||
* @var int Temperature boost value | ||
* In case, when AR has no rankBoostAttribute attribute, boost with a default value | ||
*/ | ||
public $boost = 0; | ||
|
||
/** | ||
* @var int Cooling rate | ||
* How many hours it should take for the temperature to fall by roughly half | ||
*/ | ||
public $coolingRate = 150; | ||
|
||
/** | ||
* @var mixed In case, when the rankValue is `null` it will math this formula: | ||
* (Current T) = (Last recorded T) × exp( -(Cooling rate) × (Hours since last recorded T) ) | ||
*/ | ||
public $rankValue; | ||
|
||
/** | ||
* @var mixed In case, when the value is `null`, the result of the PHP function [time()](http://php.net/manual/en/function.time.php) | ||
* will be used as value. | ||
*/ | ||
public $timeValue; | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
public function events() | ||
{ | ||
return [ | ||
\yii\db\BaseActiveRecord::EVENT_BEFORE_INSERT => 'beforeInsert', | ||
]; | ||
} | ||
|
||
/** | ||
* Set initial data on before insert | ||
*/ | ||
public function beforeInsert($event) | ||
{ | ||
$this->owner->{$this->rankAttribute} = $this->getRankValue($this->initial, $event); | ||
$this->owner->{$this->rankTimeAttribute} = $this->getTimeValue($event); | ||
} | ||
|
||
/** | ||
* Calculate current temperature | ||
* (Current T) = (Last recorded T) × exp( -(Cooling rate) × (Hours since last recorded T) ) | ||
* | ||
* @param float $rank Last recorded temperature | ||
* @param int $time Time of last recorded temperature update | ||
* @return float | ||
*/ | ||
public function calculateRank($rank, $time) | ||
{ | ||
$time = is_int($time) ? $time : strtotime($time); | ||
$diff = time() - $time; | ||
|
||
if ($diff > 0) { | ||
$hours = floor($diff / 60 / 60); | ||
$rank = $rank * exp(-(1 / $this->coolingRate) * $hours); | ||
} | ||
|
||
return $rank; | ||
} | ||
|
||
/** | ||
* Get current rank | ||
* @param float $up Temperature increment value | ||
* @param Event|null $event the event that triggers the current attribute updating. | ||
* @return float the temperature value | ||
*/ | ||
protected function getRankValue($up, $event) | ||
{ | ||
if ($this->rankValue === null) { | ||
// Just return a value | ||
if ($this->owner->isNewRecord) { | ||
return $up; | ||
} else { | ||
$rank = $this->calculateRank($this->owner->{$this->rankAttribute}, $this->owner->{$this->rankTimeAttribute}); | ||
return ($rank + $up); | ||
} | ||
} elseif ($this->rankValue instanceof Closure || is_array($this->rankValue) && is_callable($this->rankValue)) { | ||
return call_user_func($this->rankValue, $up, $event); | ||
} | ||
|
||
return $this->rankValue; | ||
} | ||
|
||
/** | ||
* In case, when the [[timeValue]] is `null`, the result of the PHP function [time()](http://php.net/manual/en/function.time.php) | ||
* will be used as value. | ||
* @return mixed | ||
*/ | ||
protected function getTimeValue($event) | ||
{ | ||
if ($this->timeValue === null) { | ||
return time(); | ||
} elseif ($this->timeValue instanceof Closure || is_array($this->timeValue) && is_callable($this->timeValue)) { | ||
return call_user_func($this->timeValue, $event); | ||
} | ||
|
||
return $this->timeValue; | ||
} | ||
|
||
/** | ||
* Updates a temperature with specified value | ||
* | ||
* ```php | ||
* $model->heat(10); | ||
* ``` | ||
* @param float $up the heat up value to update | ||
* @throws InvalidCallException if owner is a new record. | ||
*/ | ||
public function heat($up) | ||
{ | ||
/* @var $owner BaseActiveRecord */ | ||
$owner = $this->owner; | ||
if ($owner->getIsNewRecord()) { | ||
throw new InvalidCallException('Updating the rank is not possible on a new record.'); | ||
} | ||
$owner->{$this->rankAttribute} = $this->getRankValue($up, null); | ||
$owner->{$this->rankTimeAttribute} = $this->getTimeValue(null); | ||
$owner->update(false, [$this->rankAttribute, $this->rankTimeAttribute]); | ||
} | ||
|
||
/** | ||
* Boost a temperature with specified value | ||
* | ||
* ```php | ||
* $model->boost(); | ||
* ``` | ||
* @param float $up the heat up value to update | ||
* In case when up value is null, checks AR for rankBoostAttribute or uses default boost value | ||
* @throws InvalidCallException if owner is a new record. | ||
*/ | ||
public function boost($up = null) | ||
{ | ||
if ($up === null) { | ||
if ($this->owner->hasProperty($this->rankBoostAttribute)) { | ||
$up = $this->owner->{$this->rankBoostAttribute}; | ||
} else { | ||
$up = $this->boost; | ||
} | ||
} | ||
$this->heat($up); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,83 @@ | ||
# yii2-newton-cool-ranking-behavior | ||
Yii2 Newton cool ranking behavior based on Newton's Law of Cooling | ||
# Yii2 Newton Cool Ranking Behavior | ||
|
||
This behavior provides the algorithm of rank hotness with Newton's law of cooling | ||
explained by [Evan Miller](http://www.evanmiller.org/). | ||
|
||
You can use it to rate comments or blog posts. Listing active discussion threads in an online forum. | ||
|
||
Read this article [Rank Hotness With Newton's Law of Cooling](http://www.evanmiller.org/rank-hotness-with-newtons-law-of-cooling.html) for more details. | ||
|
||
## Installation | ||
|
||
Package is available on [Packagist](https://packagist.org/packages/sizeg/yii2-newton-cool-ranking-behavior), | ||
you can install it using [Composer](http://getcomposer.org). | ||
|
||
```shell | ||
composer require sizeg/yii2-newton-cool-ranking-behavior | ||
``` | ||
|
||
### Dependencies | ||
|
||
- Yii2 (testing with 2.8, but should work with lower versions) | ||
|
||
## Basic usage | ||
|
||
Create migration, | ||
|
||
```php | ||
public function up() | ||
{ | ||
// [[NewtonCoolRankingBehavior::$rankAttribute]] | ||
$this->addColumn('{{%tableName}}', 'rank', $this->float()); | ||
|
||
// [[NewtonCoolRankingBehavior::$rankTimeAttribute]] | ||
// By default time update with result of php time() function | ||
// For example we will use DateTime instead of UnixTimestamp | ||
$this->addColumn('{{%tableName}}', 'rankTime', $this->datetime()); | ||
|
||
// [[NewtonCoolRankingBehavior::$rankBoostAttribute]] | ||
// This field is optional | ||
$this->addField('{{%tableName}}', 'rankBoost', $this->float()); | ||
} | ||
|
||
``` | ||
|
||
Add behavior to your ActiveRecord model, | ||
|
||
```php | ||
class Item extends \yii\base\ActiveRecord | ||
{ | ||
public function behaviors() | ||
{ | ||
return \yii\helpers\ArrayHelper::merge(parent::behaviors(), [ | ||
[ | ||
'class' => 'sizeg\newtoncoolranking\NewtonCoolRankingBehavior', | ||
// optional params | ||
'initial' => 1000, | ||
'coolingRate' => 150, | ||
'timeValue' => date('Y-m-d H:i:s'), // can be a callback function | ||
] | ||
]); | ||
} | ||
} | ||
``` | ||
|
||
By default the new model would have [[NewtonCoolRankingBehavior::$initial]] value | ||
and will cooling with [[NewtonCoolRankingBehavior::$coolingRate]]. | ||
|
||
When there is new activity on an model, you need update rank, | ||
|
||
```php | ||
/** @var ActiveRecord $model */ | ||
$model->heat(20); | ||
``` | ||
|
||
Sometimes you need one or more models to show in top for a few days, then you need to boost it. | ||
|
||
Boost value will be received from model [[NewtonCoolRankingBehavior::$rankBoostAttribute]] field. | ||
If field doesn't exist, the value will be received from optional [[NewtonCoolRankingBehavior::$boost]] attribute. | ||
|
||
```php | ||
/** @var ActiveRecord $model */ | ||
$model->boost(); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
"name": "sizeg/yii2-newton-cool-ranking-behavior", | ||
"description": "Yii2 Newton cool ranking behavior", | ||
"type": "yii2-extension", | ||
"keywords": [ | ||
"yii2", | ||
"newton", | ||
"behavior" | ||
], | ||
"homepage": "https://github.com/sizeg/yii2-newton-cool-ranking-behavior", | ||
"license": "MIT", | ||
"authors": [ | ||
{ | ||
"name": "Dmitry Demin", | ||
"email": "[email protected]", | ||
"homepage": "http://size.perm.ru", | ||
"role": "Developer" | ||
} | ||
], | ||
"support": { | ||
"issues": "https://github.com/sizeg/yii2-newton-cool-ranking-behavior/issues", | ||
"source": "https://github.com/sizeg/yii2-newton-cool-ranking-behavior" | ||
}, | ||
"require": { | ||
"yiisoft/yii2": "*" | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"sizeg\\newtoncoolranking\\": "" | ||
} | ||
} | ||
} |