Completed
Pull Request — master (#178)
by Corey
02:50
created

UserBehavior::getCustomBehavior()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 2
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace common\models;
4
5
use Yii;
6
use \common\interfaces\TimeInterface;
7
use \common\interfaces\BehaviorInterface;
8
use \common\interfaces\UserBehaviorInterface;
9
use \common\components\ActiveRecord;
10
use yii\db\Query;
11
use yii\helpers\ArrayHelper as AH;
12
use \DateTime;
13
use \DateTimeZone;
14
use yii\db\Expression;
15
16
/**
17
 * This is the model class for table "user_behavior_link".
18
 *
19
 * @property integer $id
20
 * @property integer $user_id
21
 * @property integer $behavior_id
22
 * @property integer $category_id
23
 * @property string $date
24
 *
25
 * @property Behavior $user
26
 */
27
class UserBehavior extends ActiveRecord implements UserBehaviorInterface
28
{
29
  private $time;
30
  private $behavior;
31
32
  public function __construct(BehaviorInterface $behavior, TimeInterface $time, $config = []) {
33
    $this->behavior = $behavior;
34
    $this->time = $time;
35
    parent::__construct($config);
36
  }
37
38
  /**
39
   * @inheritdoc
40
   */
41
  public static function tableName()
42
  {
43
    return 'user_behavior_link';
44
  }
45
46
  /**
47
   * @inheritdoc
48
   */
49
  public function rules()
50
  {
51
    return [
52
      [['user_id', 'behavior_id', 'category_id', 'date'], 'required'],
53
      [['user_id', 'behavior_id', 'category_id'], 'integer'],
54
    ];
55
  }
56
57
  /**
58
   * @inheritdoc
59
   */
60
  public function attributeLabels()
61
  {
62
    return [
63
      'id'        => 'ID',
64
      'date'      => 'Date',
65
      'user_id'   => 'User ID',
66
      'behavior_id' => 'Behavior ID',
67
      'category_id' => 'Category ID',
68
    ];
69
  }
70
71
  /**
72
   * @return \yii\db\ActiveQuery
73
   */
74
  public function getUser()
75
  {
76
    return $this->hasOne(\common\models\User::class, ['id' => 'user_id']);
77
  }
78
79
  /**
80
   * @return \yii\db\ActiveQuery
81
   */
82
  public function getCustomBehavior() {
83
    return $this->hasOne(\common\models\CustomBehavior::class, ['custom_behavior_id' => 'id']);
84
  }
85
86
  public function getPastCheckinDates() {
87
    $past_checkin_dates = [];
88
    $query = new Query;
89
    $query->params = [":user_id" => Yii::$app->user->id];
90
    $query->select("date")
91
      ->from('user_behavior_link l')
92
      ->groupBy('date, user_id')
93
      ->having('user_id = :user_id');
94
    $temp_dates = $query->all();
95
    foreach($temp_dates as $temp_date) {
96
      $past_checkin_dates[] = $this->time->convertUTCToLocal($temp_date['date']);
97
    }
98
99
    return $past_checkin_dates;
100
  }
101
102
  public function getUserBehaviorsWithCategory($checkin_date) {
103
    list($start, $end) = $this->time->getUTCBookends($checkin_date);
104
105
    $query = new Query;
106
    $query->select('*')
107
      ->from('user_behavior_link')
108
      ->orderBy('behavior_id')
109
      ->where("user_id=:user_id
110
          AND date > :start_date
111
          AND date < :end_date",
112
      [
113
        ":user_id" => Yii::$app->user->id, 
114
        ":start_date" => $start, 
115
        ":end_date" => $end
116
      ]);
117
118
    $user_behaviors = self::decorate($query->all());
119
    return AH::map($user_behaviors, 'id', function($a) { return AH::getValue($a, 'behavior.name', $a['custom_behavior']); }, function($b) {return $b['category_id']; });
0 ignored issues
show
Bug introduced by
$user_behaviors of type common\models\an is incompatible with the type array expected by parameter $array of yii\helpers\BaseArrayHelper::map(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

119
    return AH::map(/** @scrutinizer ignore-type */ $user_behaviors, 'id', function($a) { return AH::getValue($a, 'behavior.name', $a['custom_behavior']); }, function($b) {return $b['category_id']; });
Loading history...
120
  }
121
122
  public function getCheckinBreakdown(int $period = 30) {
123
    $datetimes = $this->time->getDateTimesInPeriod($period);
0 ignored issues
show
Bug introduced by
The method getDateTimesInPeriod() does not exist on common\interfaces\TimeInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to common\interfaces\TimeInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

123
    /** @scrutinizer ignore-call */ 
124
    $datetimes = $this->time->getDateTimesInPeriod($period);
Loading history...
124
    $key = "checkins_".Yii::$app->user->id."_{$period}_".$this->time->getLocalDate();
125
    $checkins = Yii::$app->cache->get($key);
126
127
    if($checkins === false) {
128
      $checkins = [];
129
      foreach($datetimes as $datetime) {
130
        $behaviors = self::decorate($this->getBehaviorsWithCounts($datetime));
131
        $checkins[$datetime->format('Y-m-d')] = $this->getBehaviorsByCategory($behaviors);
0 ignored issues
show
Bug introduced by
$behaviors of type common\models\an is incompatible with the type array expected by parameter $decorated_behaviors of common\models\UserBehavi...etBehaviorsByCategory(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

131
        $checkins[$datetime->format('Y-m-d')] = $this->getBehaviorsByCategory(/** @scrutinizer ignore-type */ $behaviors);
Loading history...
132
      }
133
134
      Yii::$app->cache->set($key, $checkins, 60*60*24);
135
    }
136
137
    return $checkins;
138
  }
139
140
  /**
141
   * @param integer $limit the desired number of behaviors
142
   * @return array a list of the most-selected behaviors
143
   */
144
  public function getTopBehaviors(int $limit = 5) {
145
    return self::decorate($this->getBehaviorsWithCounts($limit));
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::decorate($t...iorsWithCounts($limit)) returns the type common\models\an which is incompatible with the documented return type array.
Loading history...
146
  }
147
148
  /**
149
   * @param array $decorated_behaviors an array of behaviors ran through self::decorate()
150
   * @return Array a list of categories and the number of selected behaviors in each category
151
   */
152
  public function getBehaviorsByCategory(array $decorated_behaviors) {
153
    $arr = array_reduce($decorated_behaviors, function($acc, $row) {
154
      $cat_id = $row['category_id'];
155
      if(array_key_exists($cat_id, $acc)) {
156
        $acc[$cat_id]['count'] += $row['count'];
157
      } else {
158
        $acc[$cat_id] = [
159
          'name'      => $row['category']['name'],
160
          'count'     => $row['count'],
161
          'color'     => \common\models\Category::$colors[$cat_id]['color'],
162
          'highlight' => \common\models\Category::$colors[$cat_id]['highlight'],
163
        ];
164
      }
165
      return $acc;
166
    }, []);
167
    ksort($arr);
168
    return $arr;
169
  }
170
171
  /*
172
   * @param integer|\DateTime $limit a limit for the number of behaviors to
173
   * return if an integer is supplied. If a \DateTime object is given instead,
174
   * then it adds a start and end date range in the WHERE clause of the query.
175
   * @return array a list of the user's behaviors with a count of each behavior's
176
   * occurence, sorted in descending order of occurrence.
177
   */
178
  public function getBehaviorsWithCounts($limit = null) {
179
    $query = new Query;
180
    $query->params = [":user_id" => Yii::$app->user->id];
181
    $query->select("user_id, behavior_id, category_id, COUNT(id) as count")
182
      ->from('user_behavior_link')
183
      ->groupBy('behavior_id, category_id, user_id')
184
      ->having('user_id = :user_id')
185
      ->orderBy('count DESC');
186
187
    if($limit instanceof \DateTime) {
188
      list($start, $end) = $this->time->getUTCBookends($limit->format('Y-m-d'));
189
      $query->params += [':start_date' => $start, ':end_date' => $end];
190
      $query->where('user_id=:user_id AND date > :start_date AND date <= :end_date');
191
    } else if(is_int($limit)) {
192
      $query->limit($limit);
193
    }
194
    return $query->all();
195
  }
196
197
  /**
198
   * Returns an array of behaviors selected by the given user on the given date.
199
   * NOTE: this function is designed to return data in the same format that is returned by using yii\helpers\ArrayHelper::index(\common\models\Behavior::$behaviors, 'name', "category_id"). This facilitates generation of the CheckinForm and allows us to easily merge these two datasets together.
200
   * @param integer $user_id
201
   * @param string|null $local_date
202
   * @return array
203
   */
204
  public function getByDate(int $user_id, $local_date = null) {
205
    if(is_null($local_date)) $local_date = $this->time->getLocalDate();
206
207
    $behaviors = $this->getBehaviorData($user_id, $local_date);
208
    $behaviors = self::decorate($behaviors);
209
    return $this->parseBehaviorData($behaviors);
210
  }
211
212
  /**
213
   * Adds the respective Category and Behavior to each UserBehavior in the given array.
214
   * @param array of UserBehaviors
0 ignored issues
show
Bug introduced by
The type common\models\of was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
215
   * @return an array of decorated UserBehaviors, each with an added Category and Behavior
0 ignored issues
show
Bug introduced by
The type common\models\an was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
216
   */
217
  public static function decorate(array $uo) {
218
    foreach($uo as &$o) {
219
      $behavior = \common\models\Behavior::getBehavior('id', $o['behavior_id']);
220
      $category = \common\models\Category::getCategory('id', $o['category_id']);
221
      if($behavior) {
222
        $o['behavior'] = $behavior;
223
      }
224
      if($category) {
225
        $o['category'] = $category;
226
      }
227
    }
228
    return $uo;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $uo returns the type array which is incompatible with the documented return type common\models\an.
Loading history...
229
  }
230
231
  // TODO: this should probably be a private method...but unit testing is hard
232
  public function getBehaviorData(int $user_id, $local_date) {
0 ignored issues
show
Unused Code introduced by
The parameter $user_id is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

232
  public function getBehaviorData(/** @scrutinizer ignore-unused */ int $user_id, $local_date) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
233
    list($start, $end) = $this->time->getUTCBookends($local_date);
234
235
    return $this->find()
236
      ->where("user_id=:user_id
237
      AND date > :start_date
238
      AND date < :end_date",
239
    [
240
      "user_id" => Yii::$app->user->id,
241
      ':start_date' => $start,
242
      ":end_date" => $end
243
    ])
244
    ->asArray()
245
    ->all();
246
  }
247
248
  // TODO: this should probably be a private method...but unit testing is hard
249
  public function parseBehaviorData($behaviors) {
250
    if(!$behaviors) return [];
251
252
    $bhvrs_by_cat = [];
253
    foreach($behaviors as $behavior) {
254
      $indx = $behavior['category_id'];
255
256
      $bname = AH::getValue($behavior, 'behavior.name', $behavior['custom_behavior']);
257
      $bhvrs_by_cat[$indx][$bname] = [
258
        "id" => $behavior['behavior_id'],
259
        "name"=>$bname
260
      ];
261
    }
262
263
    return $bhvrs_by_cat;
264
  }
265
}
266