Skip to content

Commit

Permalink
Added behavior and updated readme.
Browse files Browse the repository at this point in the history
  • Loading branch information
Dmitry Demin committed Jun 21, 2016
1 parent 9bcffd3 commit 272e3ed
Show file tree
Hide file tree
Showing 3 changed files with 295 additions and 2 deletions.
180 changes: 180 additions & 0 deletions NewtonCoolRankingBehavior.php
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);
}
}
85 changes: 83 additions & 2 deletions README.md
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();
```
32 changes: 32 additions & 0 deletions composer.json
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\\": ""
}
}
}

0 comments on commit 272e3ed

Please sign in to comment.