Skip to content

Instantly share code, notes, and snippets.

@alexey-m-ukolov
Created January 5, 2017 07:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexey-m-ukolov/9588dcefc95863d1c6df33b3db909dde to your computer and use it in GitHub Desktop.
Save alexey-m-ukolov/9588dcefc95863d1c6df33b3db909dde to your computer and use it in GitHub Desktop.
<?php
namespace App\Entities\TokenActions;
use App\Entities\User;
/**
* @property string $route
* @property array $parameters
* @property string $anchor
*/
class Redirect extends TokenAction
{
protected static $typeCode = 'redirect';
protected static $expiresInDays = 100;
protected static $canBeExecutedOnlyOnce = false;
/** @noinspection PhpDocSignatureInspection
* @param User $user
* @param array $routeName
* @param array $routeParameters
* @param string $anchor
*
* @return Redirect
*/
public static function makeFor(User $user)
{
// Чтобы узнать зачем костыль, смотри родительский метод
$args = func_get_args();
$options = [
'route' => isset($args[1]) ? $args[1] : 'home',
'parameters' => isset($args[2]) && is_array($args[2]) ? $args[2] : [],
'anchor' => isset($args[3]) ? $args[3] : '',
];
return parent::makeFor($user, $options);
}
public function getRouteAttribute()
{
return isset($this->options['route']) ? $this->options['route'] : 'home';
}
public function setRouteAttribute($value)
{
$options = $this->options;
$options['route'] = $value;
$this->options = $options;
}
public function getParametersAttribute()
{
return isset($this->options['parameters']) ? $this->options['parameters'] : [];
}
public function setParametersAttribute(array $value)
{
$options = $this->options;
$options['parameters'] = $value;
$this->options = $options;
}
public function getAnchorAttribute()
{
return isset($this->options['anchor']) ? $this->options['anchor'] : '';
}
public function setAnchorAttribute($value)
{
$options = $this->options;
$options['anchor'] = $value;
$this->options = $options;
}
/**
* @inheritdoc
*/
protected function getRouteName()
{
return $this->route;
}
/**
* @inheritdoc
*/
protected function getRouteParameters()
{
return $this->parameters;
}
/**
* @inheritdoc
*/
protected function getRouteAnchor()
{
return $this->anchor;
}
}
<?php
namespace App\Entities\TokenActions;
use Carbon\Carbon;
use Eloquent;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Bus\Dispatcher as JobsDispatcher;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Query\Builder;
use Illuminate\Events\Dispatcher;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Str;
use App\Entities\User;
use App\Events\TokenAction\Failed;
use App\Events\TokenAction\IsExecuting;
use App\Events\TokenAction\WasExecuted;
use App\Exceptions\TokenAction\AlreadyExecuted;
use App\Exceptions\TokenAction\Expired;
use App\Exceptions\TokenAction\InvalidType;
use App\Exceptions\TokenAction\UserMismatch;
use App\Jobs\Job;
/**
* @property string $token
* @property int $user_id
* @property string $type
* @property array $options
* @property Carbon $expires_at
* @property Carbon $executed_at
* @property Carbon $created_at
* @property Carbon $updated_at
* @property User $user
* @method static Builder|TokenAction whereToken($value)
*/
class TokenAction extends Eloquent
{
/**
* @var string
*/
protected static $typeCode;
/**
* @var int
*/
protected static $expiresInDays = 3;
/**
* @var bool
*/
protected static $canBeExecutedOnlyOnce = true;
/**
* @var string
*/
protected static $defaultAnchor = '';
/**
* @var array
*/
private static $typeToClassMap = [
'redirect' => Redirect::class,
'accept_friendship_offer' => FriendshipOffer\Accept::class,
'reject_friendship_offer' => FriendshipOffer\Reject::class,
];
/**
* @var bool
*/
public $incrementing = false;
/**
* Нужно явно указать название таблицы, чтобы потомки класса пытались сохраниться туда, куда нужно
*
* @var string
*/
protected $table = 'token_actions';
/**
* @var string
*/
protected $primaryKey = 'token';
/**
* @var array
*/
protected $dates = ['expires_at', 'executed_at', 'created_at', 'updated_at'];
/**
* @var array
*/
protected $casts = [
'options' => 'array',
];
/**
* @var Guard
*/
protected $auth;
/**
* @var Dispatcher
*/
protected $events;
/**
* @var JobsDispatcher
*/
protected $jobs;
/**
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->auth = app(Guard::class);
$this->events = app('events');
$this->jobs = app(JobsDispatcher::class);
}
/**
* @param User $user
*
* @return self
*/
public static function makeFor(User $user)
{
/** @var self $model */
$model = new static;
// Поскольку strict standard не позволяет переопределённым в дочерних классах методам иметь другую сигнатуру,
// приходится извращаться
if (func_num_args() >= 2) {
$options = (array) func_get_arg(1);
} else {
$options = [];
}
$model->type = static::$typeCode;
$model->token = static::generateToken();
$model->expires_at = (new Carbon())->addDays(static::$expiresInDays);
$model->options = $options;
$model->user()->associate($user);
$model->save();
return $model;
}
/**
* @return string
*/
private static function generateToken()
{
return sprintf('%s-%s', time(), Str::random(32));
}
/**
* @return BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
/** @noinspection PhpDocMissingThrowsInspection
* @return null
* @throws AlreadyExecuted
* @throws Expired
* @throws UserMismatch
*/
public function execute()
{
$this->events->fire(new IsExecuting($this));
try {
$this->checkThatCanBeExecuted();
$this->checkIfExpired();
$this->logUserIn();
} catch (\Exception $e) {
$this->events->fire(new Failed($this, get_class($e)));
throw $e;
}
$this->executed_at = Carbon::now();
$this->save();
$job = $this->getJob();
if ($job) {
$this->jobs->dispatch($job);
}
$this->events->fire(new WasExecuted($this));
}
/**
* @throws AlreadyExecuted
*/
private function checkThatCanBeExecuted()
{
if (!$this->canBeExecuted()) {
throw new AlreadyExecuted;
}
}
/**
* @return bool
*/
private function canBeExecuted()
{
return !static::$canBeExecutedOnlyOnce || !$this->executed_at;
}
/**
* @throws Expired
*/
private function checkIfExpired()
{
if ($this->isExpired()) {
throw new Expired;
}
}
/**
* @return bool
*/
private function isExpired()
{
return is_a($this->expires_at, Carbon::class) && $this->expires_at->isPast();
}
/**
* @throws UserMismatch
*/
private function logUserIn()
{
$this->checkIfUserCanBeLoggedIn();
$this->auth->loginUsingId($this->user_id);
}
/**
* @throws UserMismatch
*/
private function checkIfUserCanBeLoggedIn()
{
/** @var User $user */
$user = $this->auth->user();
if ($user && !$user->owns($this)) {
throw new UserMismatch;
}
}
/**
* @return Job|null
*/
protected function getJob()
{
return null;
}
/**
* @return RedirectResponse|Redirector
*/
public function getRedirect()
{
return redirect(route($this->getRouteName(), $this->getRouteParameters()) . $this->getRouteAnchor());
}
/**
* @return string
*/
protected function getRouteName()
{
return route('home');
}
/**
* @return array
*/
protected function getRouteParameters()
{
return [];
}
/**
* @return string
*/
protected function getRouteAnchor()
{
return '';
}
/**
* Create a new model instance that is existing.
*
* @param \stdClass|array $attributes
* @param string|null $connection
*
* @return static
* @throws InvalidType
*/
public function newFromBuilder($attributes = [], $connection = null)
{
$class = $this->getEntityClass($attributes);
/** @var static $model */
$model = new $class;
$model->exists = true;
$model->setRawAttributes((array) $attributes, true);
$model->setConnection($connection ?: $this->connection);
return $model;
}
/**
* @param $attributes
*
* @return null
* @throws InvalidType
*/
private function getEntityClass($attributes)
{
$code = $attributes->type;
if (array_key_exists($code, static::$typeToClassMap) && class_exists($class = static::$typeToClassMap[$code])) {
return $class;
}
throw new InvalidType($code);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment