Completed
Pull Request — master (#180)
by Corey
03:00
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
   * @codeCoverageIgnore
41
   */
42
  public static function tableName()
43
  {
44
    return 'user_behavior_link';
45
  }
46
47
  /**
48
   * @inheritdoc
49
   * @codeCoverageIgnore
50
   */
51
  public function rules()
52
  {
53
    return [
54
      [['user_id', 'behavior_id', 'category_id', 'date'], 'required'],
55
      [['user_id', 'behavior_id', 'category_id'], 'integer'],
56
      ['custom_behavior', 'string'],
57
    ];
58
  }
59
60
  /**
61
   * @inheritdoc
62
   * @codeCoverageIgnore
63
   */
64
  public function attributeLabels()
65
  {
66
    return [
67
      'id'        => 'ID',
68
      'date'      => 'Date',
69
      'user_id'   => 'User ID',
70
      'behavior_id' => 'Behavior ID',
71
      'category_id' => 'Category ID',
72
      'custom_behavior' => 'Personal Behavior Name',
73
    ];
74
  }
75
76
  /**
77
   * @return \yii\db\ActiveQuery
78
   * @codeCoverageIgnore
79
   */
80
  public function getUser()
81
  {
82
    return $this->hasOne(\common\models\User::class, ['id' => 'user_id']);
83
  }
84
85
  public function getPastCheckinDates() {
86
    $past_checkin_dates = [];
87
    $query = new Query;
88
    $query->params = [":user_id" => Yii::$app->user->id];
89
    $query->select("date")
90
      ->from('user_behavior_link l')
91
      ->groupBy('date, user_id')
92
      ->having('user_id = :user_id');
93
    $temp_dates = $query->all();
94
    foreach($temp_dates as $temp_date) {
95
      $past_checkin_dates[] = $this->time->convertUTCToLocal($temp_date['date']);
96
    }
97
98
    return $past_checkin_dates;
99
  }
100
101
  public function getUserBehaviorsWithCategory($checkin_date) {
102
    list($start, $end) = $this->time->getUTCBookends($checkin_date);
103
104
    $query = new Query;
105
    $query->select('*')
106
      ->from('user_behavior_link')
107
      ->orderBy('behavior_id')
108
      ->where("user_id=:user_id
109
          AND date > :start_date
110
          AND date < :end_date",
111
      [
112
        ":user_id" => Yii::$app->user->id, 
113
        ":start_date" => $start, 
114
        ":end_date" => $end
115
      ]);
116
117
    $user_behaviors = self::decorate($query->all());
118
    return AH::map($user_behaviors, '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

118
    return AH::map(/** @scrutinizer ignore-type */ $user_behaviors, 'id',
Loading history...
119
      function($a) {
120
        return AH::getValue($a, 'behavior.name', $a['custom_behavior']);
121
      },
122
      function($b) {
123
        return $b['category_id'];
124
      });
125
  }
126
127
  public function getCheckinBreakdown(int $period = 30) {
128
    $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

128
    /** @scrutinizer ignore-call */ 
129
    $datetimes = $this->time->getDateTimesInPeriod($period);
Loading history...
129
    $key = "checkins_".Yii::$app->user->id."_{$period}_".$this->time->getLocalDate();
130
    $checkins = Yii::$app->cache->get($key);
131
132
    if($checkins === false) {
133
      $checkins = [];
134
      foreach($datetimes as $datetime) {
135
        $behaviors = self::decorate($this->getBehaviorsWithCounts($datetime));
136
        $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

136
        $checkins[$datetime->format('Y-m-d')] = $this->getBehaviorsByCategory(/** @scrutinizer ignore-type */ $behaviors);
Loading history...
137
      }
138
139
      Yii::$app->cache->set($key, $checkins, 60*60*24);
140
    }
141
142
    return $checkins;
143
  }
144
145
  /**
146
   * @param integer $limit the desired number of behaviors
147
   * @return array a list of the most-selected behaviors
148
   */
149
  public function getTopBehaviors(int $limit = 5) {
150
    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...
151
  }
152
153
  /**
154
   * @param array $decorated_behaviors an array of behaviors ran through self::decorate()
155
   * @return Array a list of categories and the number of selected behaviors in each category
156
   */
157
  public function getBehaviorsByCategory(array $decorated_behaviors) {
158
    $arr = array_reduce($decorated_behaviors, function($acc, $row) {
159
      $cat_id = $row['category_id'];
160
      if(array_key_exists($cat_id, $acc)) {
161
        $acc[$cat_id]['count'] += $row['count'];
162
      } else {
163
        $acc[$cat_id] = [
164
          'name'      => $row['category']['name'],
165
          'count'     => $row['count'],
166
          'color'     => \common\models\Category::$colors[$cat_id]['color'],
167
          'highlight' => \common\models\Category::$colors[$cat_id]['highlight'],
168
        ];
169
      }
170
      return $acc;
171
    }, []);
172
    ksort($arr);
173
    return $arr;
174
  }
175
176
  /*
177
   * @param integer|\DateTime $limit a limit for the number of behaviors to
178
   * return if an integer is supplied. If a \DateTime object is given instead,
179
   * then it adds a start and end date range in the WHERE clause of the query.
180
   * @return array a list of the user's behaviors with a count of each behavior's
181
   * occurence, sorted in descending order of occurrence.
182
   */
183
  public function getBehaviorsWithCounts($limit = null) {
184
    $query = new Query;
185
    $query->params = [":user_id" => Yii::$app->user->id];
186
    $query->select("user_id, behavior_id, category_id, custom_behavior, COUNT(id) as count")
187
      ->from('user_behavior_link')
188
      ->groupBy('behavior_id, category_id, custom_behavior, user_id')
189
      ->having('user_id = :user_id')
190
      ->orderBy('count DESC');
191
192
    if($limit instanceof \DateTime) {
193
      list($start, $end) = $this->time->getUTCBookends($limit->format('Y-m-d'));
194
      $query->params += [':start_date' => $start, ':end_date' => $end];
195
      $query->where('user_id=:user_id AND date > :start_date AND date <= :end_date');
196
    } else if(is_int($limit)) {
197
      $query->limit($limit);
198
    }
199
    return $query->all();
200
  }
201
202
  /**
203
   * Returns an array of behaviors selected by the given user on the given date.
204
   * NOTE: this function is designed to return data in the same format that is
205
   * returned by using
206
   * yii\helpers\ArrayHelper::index(\common\models\Behavior::$behaviors, 'name', "category_id").
207
   * This facilitates generation of the CheckinForm and allows us to easily
208
   * merge these two datasets together.
209
   * @param integer $user_id
210
   * @param string|null $local_date
211
   * @return array
212
   */
213
  public function getByDate(int $user_id, $local_date = null) {
214
    if(is_null($local_date)) $local_date = $this->time->getLocalDate();
215
216
    $behaviors = $this->getBehaviorData($user_id, $local_date);
217
    $behaviors = self::decorate($behaviors);
218
    return $this->parseBehaviorData($behaviors);
219
  }
220
221
  /**
222
   * Adds the respective Category and Behavior to each UserBehavior in the given array.
223
   * @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...
224
   * @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...
225
   */
226
  public static function decorate(array $uo) {
227
    foreach($uo as &$o) {
228
      $behavior = \common\models\Behavior::getBehavior('id', $o['behavior_id']);
229
      $category = \common\models\Category::getCategory('id', $o['category_id']);
230
      if($behavior) {
231
        $o['behavior'] = $behavior;
232
      } else if (array_key_exists('custom_behavior', $o)) {
233
        // if a behavior isn't set, this may be a custom_behavior
234
        $o['behavior'] = [
235
          // we don't have 'id' in here...this is a custom_behavior instead of a 'real' one
236
          'name' => $o['custom_behavior'],
237
        ];
238
      } // else -- something is weird
239
      if($category) {
240
        $o['category'] = $category;
241
      }
242
    }
243
    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...
244
  }
245
246
  // TODO: this should probably be a private method...but unit testing is hard
247
  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

247
  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...
248
    list($start, $end) = $this->time->getUTCBookends($local_date);
249
250
    return $this->find()
251
      ->where("user_id=:user_id
252
      AND date > :start_date
253
      AND date < :end_date",
254
    [
255
      "user_id" => Yii::$app->user->id,
256
      ':start_date' => $start,
257
      ":end_date" => $end
258
    ])
259
    ->asArray()
260
    ->all();
261
  }
262
263
  // TODO: this should probably be a private method...but unit testing is hard
264
  public function parseBehaviorData($behaviors) {
265
    if(!$behaviors) return [];
266
267
    $bhvrs_by_cat = [];
268
    foreach($behaviors as $behavior) {
269
      $indx = $behavior['category_id'];
270
271
      $bname = AH::getValue($behavior, 'behavior.name', AH::getValue($behavior, 'custom_behavior'));
272
      $bhvrs_by_cat[$indx][$bname] = [
273
        "id" => $behavior['behavior_id'],
274
        "name"=>$bname
275
      ];
276
    }
277
278
    return $bhvrs_by_cat;
279
  }
280
}
281