Skip to content

Commit

Permalink
Merge pull request #26 from tipoff/pbdreen/feature/has-statuses
Browse files Browse the repository at this point in the history
Add HasStatuses trait and supporting code
  • Loading branch information
drewroberts authored Mar 10, 2021
2 parents 42e7500 + 0587533 commit 53e4a51
Show file tree
Hide file tree
Showing 14 changed files with 544 additions and 39 deletions.
23 changes: 6 additions & 17 deletions database/factories/StatusFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,21 @@

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use Tipoff\Forms\Models\Status;
use Tipoff\Statuses\Models\Status;

class StatusFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Status::class;

/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
$word1 = $this->faker->unique->word;
$word2 = $this->faker->word;
$type = $this->faker->randomElement(['order', 'slot', 'game', 'invoice', 'payment']);
$words = $this->faker->unique()->words(2, true);

return [
'slug' => Str::slug(rand(1, 1000).'-'.$word1.'-'.$word2),
'name' => $word1.' '.$word2,
'applies_to' => $this->faker->randomElement(['order', 'slot', 'game', 'invoice', 'payment']),
'note' => $this->faker->sentences(1, true),
'name' => $words,
'type' => $type,
'note' => $this->faker->optional()->sentences(1, true),
];
}
}
29 changes: 29 additions & 0 deletions database/factories/StatusRecordFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Tipoff\Statuses\Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Tipoff\Authorization\Models\User;
use Tipoff\Statuses\Models\Status;
use Tipoff\Statuses\Models\StatusRecord;

class StatusRecordFactory extends Factory
{
protected $model = StatusRecord::class;

public function definition()
{
$statusable = User::factory()->create();
$status = randomOrCreate(Status::class);

return [
'status_id' => $status,
'type' => $status->type,
'statusable_type' => get_class($statusable),
'statusable_id' => $statusable->id,
'creator_id' => randomOrCreate(app('user')),
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ public function up()
Schema::create('statuses', function (Blueprint $table) {
$table->id();
$table->string('slug')->unique()->index();
$table->string('name')->unique();
$table->string('applies_to')->default('order'); // Values include 'order', 'slot', 'game', 'invoice', 'payment'
$table->string('name');
$table->string('type'); // Typically, full class name for model using status
$table->text('note')->nullable();
$table->timestamps();

$table->unique(['name', 'type']);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateStatusablesTable extends Migration
class CreateStatusRecordsTable extends Migration
{
public function up()
{
Schema::create('statusables', function (Blueprint $table) {
Schema::create('status_records', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(app('status'))->index();
$table->morphs('statusable');
$table->string('type'); // Typically, full class name for model using status
$table->foreignIdFor(app('user'), 'creator_id');
$table->timestamps();
$table->timestamp('created_at');
});
}
}
9 changes: 9 additions & 0 deletions src/Exceptions/StatusException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Tipoff\Statuses\Exceptions;

interface StatusException
{
}
15 changes: 15 additions & 0 deletions src/Exceptions/UnknownStatusException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Tipoff\Statuses\Exceptions;

use Throwable;

class UnknownStatusException extends \InvalidArgumentException implements StatusException
{
public function __construct(string $type, string $name, $code = 0, Throwable $previous = null)
{
parent::__construct("Unknown status value '{$name}' for status type {$type}", $code, $previous);
}
}
77 changes: 76 additions & 1 deletion src/Models/Status.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,87 @@

namespace Tipoff\Statuses\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Tipoff\Support\Models\BaseModel;
use Tipoff\Support\Traits\HasPackageFactory;

/**
* @property int id
* @property string name
* @property string type
* @property string slug
* @property string note
* @property Carbon created_at
* @property Carbon updated_at
*/
class Status extends BaseModel
{
use HasPackageFactory;

protected $casts = [];
protected $casts = [
'id' => 'integer',
];

protected $fillable = [
'type',
'name',
];

public static function publishStatuses(string $type, array $names): Collection
{
return collect($names)
->map(function (string $name) use ($type) {
return static::createStatus($type, $name);
});
}

public static function createStatus(string $type, string $name, ?string $note = null): self
{
/** @var Status $status */
$status = static::query()->firstOrNew([
'type' => $type,
'name' => $name,
]);

if ($note) {
$status->note = $note;
}

$status->save();

return $status;
}

public static function findStatus(string $type, string $name): ?self
{
/** @var Status $status */
$status = static::query()
->byType($type)
->where('name', '=', $name)
->first();

return $status;
}

protected static function boot()
{
parent::boot();

static::creating(function (Status $status) {
$status->slug = $status->slug ?: Str::slug("{$status->type}-{$status->name}");
});
}

public function scopeByType(Builder $query, string $type): Builder
{
return $query->where('type', '=', $type);
}

public function __toString()
{
return $this->name;
}
}
44 changes: 44 additions & 0 deletions src/Models/StatusRecord.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Tipoff\Statuses\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Tipoff\Authorization\Models\User;
use Tipoff\Support\Models\BaseModel;
use Tipoff\Support\Traits\HasCreator;
use Tipoff\Support\Traits\HasPackageFactory;

/**
* @property int id
* @property Status status
* @property Model statusable
* @property User creator
* @property Carbon created_at
* // Raw Relations
* @property int|null creator_id
*/
class StatusRecord extends BaseModel
{
use HasPackageFactory;
use HasCreator;

const UPDATED_AT = null;

protected $casts = [
'id' => 'integer',
'creator_id' => 'integer',
];

public function status()
{
return $this->belongsTo(Status::class);
}

public function statusable()
{
return $this->morphTo();
}
}
78 changes: 78 additions & 0 deletions src/Traits/HasStatuses.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace Tipoff\Statuses\Traits;

use Illuminate\Support\Collection;
use Tipoff\Statuses\Exceptions\UnknownStatusException;
use Tipoff\Statuses\Models\Status;
use Tipoff\Statuses\Models\StatusRecord;

/**
* @property Collection statusables
*/
trait HasStatuses
{
// When true, new statuses will be added automatically on first use
// When false, exception occurs if unknown status value is used
protected bool $dynamicStatusCreation = false;

public function getStatusHistory(?string $type = null): Collection
{
return $this->statusRecords()
->where('type', '=', $type ?? get_class($this))
->orderBy('created_at', 'desc')
->orderBy('id', 'desc')
->get();
}

public function getStatus(?string $type = null): ?Status
{
$statusRecord = $this->getStatusHistory($type)->first();

return $statusRecord ? $statusRecord->status : null;
}

public function setStatus(string $name, ?string $type = null): Status
{
$type = $type ?? get_class($this);

$status = $this->getStatusByName($name, $type);

// Check if desired status already set
if ($currentStatus = $this->getStatus($type)) {
if ($currentStatus->id === $status->id) {
return $currentStatus;
}
}

// Set status always creates new record, leaving full history behind
$statusable = new StatusRecord();
$statusable->type = $type;
$statusable->statusable()->associate($this);

$statusable->status()->associate($status)->save();
$this->load('statusRecords');

return $statusable->status;
}

public function statusRecords()
{
return $this->morphMany(StatusRecord::class, 'statusable');
}

private function getStatusByName(string $name, string $type): Status
{
if ($this->dynamicStatusCreation) {
return Status::createStatus($type, $name);
}

if ($status = Status::findStatus($type, $name)) {
return $status;
}

throw new UnknownStatusException($type, $name);
}
}
15 changes: 0 additions & 15 deletions tests/Support/Providers/NovaPackageServiceProvider.php

This file was deleted.

2 changes: 1 addition & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
use Spatie\Permission\PermissionServiceProvider;
use Tipoff\Authorization\AuthorizationServiceProvider;
use Tipoff\Statuses\StatusesServiceProvider;
use Tipoff\Statuses\Tests\Support\Providers\NovaPackageServiceProvider;
use Tipoff\Support\SupportServiceProvider;
use Tipoff\TestSupport\BaseTestCase;
use Tipoff\TestSupport\Providers\NovaPackageServiceProvider;

class TestCase extends BaseTestCase
{
Expand Down
Loading

0 comments on commit 53e4a51

Please sign in to comment.