M
M
Maxim2018-12-22 01:59:47
Yii
Maxim, 2018-12-22 01:59:47

Refactoring. What is the best way to rate a user?

Hello!
I have implemented ratings on the site. For certain actions of the user, I assign him a rating. There are several yii modules: events , appointments , news ... Each module has corresponding AR models.
To rate a user now, I use behaviors from Yii, which I plug into the AR model. In the behavior, I add a new method that will handle the event (for now I use the standard events of the AR model). In the behavior method, I place all the main logic for assigning, deleting a rating. If the logic works, then I, using the RatingUser add() model method , write the rating to the database and increase the user's rating counter.
Now I would like to optimize the code in accordance with the principle of single responsibility (each object is responsible for what it should be responsible for). I ask for your help and advice on this issue.
When I start optimizing the code, my head is spinning)) There are many questions....
1. Maybe it's better to subscribe to your events without using behaviors?
2. Can something be taken out into separate classes and inherited in each module?
3. Or to take out in a component?
In addition, it is necessary to provide for the near future ... Rating can be not only for users, but for posts, events, clubs ... The logic will be approximately the same.
Now I'll show you how everything works...
Table rating_user
id|user_id|value|rating_type_id|table|record_id|created_at
Where,
user_id is the user ID
value is the rating value
rating_type_id is the rating type
table is the name of the table for which the rating is assigned (to roll back the rating when deleting)
record_id is the ID of the record for which a rating was assigned (to roll back the rating on deletion)
created_at - the date and time of assignment
Now I record the rating through events using behaviors ...
Connection

'RatingUser' => [
 *        'class' => 'backend\modules\user\behaviors\RatingUser',
 *    ],

Behavior:
class RatingUserBehaviors extends Behavior
{
//Подписываемся на события
public function events(): array
  {
    return ArrayHelper::merge(parent::events(), [
      Appointment::EVENT_AFTER_INSERT => 'appointmentAdd',
      Appointment::EVENT_AFTER_UPDATE => 'appointmentAdd',
      Appointment::EVENT_AFTER_DELETE => 'appointmentDel',
      Event::EVENT_AFTER_INSERT => 'eventAdd',
      Event::EVENT_AFTER_DELETE => 'eventDel',
    ]);
  }

/**
   * Добавить рейтинг за назначения
   *
   * @param $event
   * @return bool
   */
  public function appointmentAdd($event)
  {
    /** @var Appointment $model */
    $model = $event->sender;
    $certification = $model->certification;
    $user_id = $certification->user_id;
    $table = $model::tableName();
    $record_id = $model->id;
    
    //Если назначение подтверждено
    if ($model->isConfirmed) {
      if ($certification->role == 'chief_judge') {
        if ($model->isAppointed) {
          $this->model->add($user_id, 4, $table, $record_id);
        } else if ($model->status == $model::STATUS_REFUSED) {
          $this->model->add($user_id, 5, $table, $record_id);
        }
      } else if ($certification->role == 'judge') {
        if ($model->status == $model::STATUS_APPOINTED) {
          $this->model->add($user_id, 1, $table, $record_id);
        } else if ($model->status == $model::STATUS_REPLASED) {
          $this->model->add($user_id, 2, $table, $record_id);
        } else if ($model->status == $model::STATUS_REFUSED) {
          $this->model->add($user_id, 3, $table, $record_id);
        }
      }
    }
    
    return true;
  }
}

Rating model:
/**
 * This is the model class for table "rating_user".
 *
 * @property int $id
 * @property int $user_id
 * @property int $value
 * @property int $rating_type_id
 * @property int $table
 * @property int $record_id
 * @property int $created_at
 * @property User $user
 * @property Profile $profile
 */
class RatingUser extends \yii\db\ActiveRecord
{
....
/**
     * Добавить рейтинг пользователю
     *
     * @param int $user_id
     * @param int $rating_type_id
     * @param string|null $table
     * @param int|null $record_id
     * @return bool
     */
    public function add(int $user_id, int $rating_type_id, string $table = null, int $record_id = null)
    {
        $value = $this->getValueType($rating_type_id);
        
        $attributes = [
            'user_id' => $user_id,
            'rating_type_id' => $rating_type_id,
            'table' => $table,
            'record_id' => $record_id,
        ];
        
        if (!$this->getExistsRatingByUser($attributes)) {
            $attributes['value'] = $value;
            $this->attributes = $attributes;
            $this->user->updateCounters(['rating_count' => $value]);
            $this->user->save(false);
            $this->save();
            
            return true;
        }
        
        return false;
    }

  /**
     * Удалить рейтинг у пользователя
     *
     * @param string|null $table
     * @param int|null $record_id
     * @return bool
     * @throws \Throwable
     * @throws \yii\db\StaleObjectException
     */
    public function del(string $table = null, int $record_id = null)
    {
        $ratings = $this::find()->where(['table' => $table, 'record_id' => $record_id])->all();
        
        foreach ($ratings as $rating) {
            $rating->user->updateCounters(['rating_count' => -$rating->value]);
            $rating->user->save();
            $rating->delete();
        }
        return true;
    }
}

Answer the question

In order to leave comments, you need to log in

2 answer(s)
A
Alexander, 2018-12-22
@Minifets

I'll give you a little advice. If you want to do "as best", then you should look towards writing tests. This will not only improve the further support of the project, but also help to understand the structure of the code. Because You will not be able to fully cover poorly structured code with tests.
And yes. The main purpose of the principles is to make life easier for the developer, not to complicate it ;).

M
Maxim Timofeev, 2018-12-23
@webinar

1. Please note that "post rating" and "user rating" can be either the same entity or different. Depending on your specific situation.
2. Behaviors do not interfere with SOLID in any way, and at the same time this does not mean that they are needed in this particular case. Since behaviors are a feature of yii, sometimes it seems that they destroy some basic principles, but this is not entirely true.
Make them separate questions and get specific answers. In this case, you need to either spend a lot of time to deal with your situation (but then this is a task, not a question), or pour water in general. I think you don't need it. Break the question down into individual specific questions.
Who needs?

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question