Skip to content

Commit

Permalink
Refactoring as service provider
Browse files Browse the repository at this point in the history
  • Loading branch information
netzknecht committed Dec 3, 2023
1 parent 04f1996 commit 0271fcd
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Lunar\Base\Migration;
use Lunar\Facades\PositionManifest;
use Lunar\Models\Attribute;
use Lunar\Models\AttributeGroup;
use Lunar\Models\ProductOption;
Expand All @@ -15,7 +16,7 @@
public function up(): void
{
/**
* Model's with position
* Model's with `HasPosition` trait
*/
$models = [
Attribute::class,
Expand All @@ -29,18 +30,17 @@ public function up(): void
foreach ($models as $model) {
$model = app($model);
Schema::table($model->getTable(), function (Blueprint $table) use ($model) {
DB::table($model->getTable())
DB::table($table->getTable())
->select(array_merge(
[$model->getKeyName()],
$model->positionUniqueConstraints()
PositionManifest::constraints($model)
))
->orderBy('position')
->orderBy('id')
->get()
->groupBy(fn (stdClass $row, int $key) =>
collect($row)
->only($model->positionUniqueConstraints())
->except('position')
->except([$model->getKeyName(), 'position'])
->join('-')
)->each
->each(fn(stdClass $row, int $key) => $row->position = $key + 1);
Expand All @@ -53,14 +53,17 @@ public function up(): void
foreach ($models as $model) {
$model = app($model);
Schema::table($model->getTable(), function (Blueprint $table) use ($model) {
$schema = Schema::getConnection()
->getDoctrineSchemaManager()
->introspectTable($model->getTable());
$uniqueIndex = $this->prefix . $table->getTable() . '_unique_position';
$uniqueConstraints = array_merge($model->positionUniqueConstraints(), ['position']);
$table->unsignedBigInteger('position')->default(null)->index()->change();
if (!$schema->hasIndex($uniqueIndex)) {
$table->unique($uniqueConstraints, $uniqueIndex);
$table->unsignedBigInteger('position')
->nullable(false)
->default(null)
->change();
$index = $table->getIndexName('index', ['position']);
if (!$table->hasIndex($index)) {
$table->dropIndex($index);
}
$index = $table->getIndexName('unique', ['position']);
if (!$table->hasIndex($index)) {
$table->unique(PositionManifest::constraints($model), $index);
}
});
}
Expand Down
140 changes: 140 additions & 0 deletions packages/core/src/Base/PositionManifest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php

namespace Lunar\Base;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Str;

class PositionManifest implements PositionManifestInterface
{
/**
* {@inheritDoc}
*/
public function saving(Model $model): void
{
if (
$model->isDirty($this->constraints($model))
|| !(intval($model->position) > 0)
|| $model->query()
->where($model->getKeyName(), '!=', $model->getKey())
->wherePosition($model->position)
->wherePositionUniqueConstraints($model->getAttributes())
->exists()
) {
$model->position = $model->query()
->where($model->getKeyName(), '!=', $model->getKey())
->wherePositionUniqueConstraints($model->getAttributes())
->max('position') + 1;
}
}

/**
*
* {@inheritDoc}
*/
public function constraints(Model $model): array
{
return array_merge(
!property_exists($model, 'positionUniqueConstraints')
|| !is_array($model->positionUniqueConstraints)
? [] : $model->positionUniqueConstraints,
['position']
);
}

/**
* {@inheritDoc}
*/
public function query(Builder $query, int $position, array $constraints = []): void
{
$query
->wherePosition($position)
->wherePositionUniqueConstraints($constraints);
}

/**
* {@inheritDoc}
*/
public function queryPosition(Builder $query, int $position): void
{
$query->where('position', $position);
}

/**
* {@inheritDoc}
*/
public function queryUniqueConstraints(Builder $query, array $constraints): void
{
$constraints = collect($constraints);
$modelConstraints = collect($this->constraints($query->getModel()))->reject('position');

if (count($modelConstraints) && !$constraints->hasAny($modelConstraints->toArray())) {
throw new \InvalidArgumentException(
sprintf(
'Position constraints "%s" for "%s" not defined!',
$modelConstraints->diff($constraints)->join('", "', '" and "'),
get_class($this)
)
);
}

$modelConstraints->each(
function ($attribute) use ($query, $constraints) {
if (method_exists($query, Str::camel('scope_' . $attribute))) {
$method = Str::camel($attribute);
} else {
$method = Str::camel('where_' . $attribute);
}
$query->{$method}($constraints[$attribute]);
}
);
}

/**
* {@inheritDoc}
*/
public static function registerBlueprintMacros(): void
{
/**
* Add a `position` column to the table and define a unique positions index
* with the given constraints
*
* The constraints can defined as array or assigned from the corsponding
* model property `positionUniqueConstraints` if model object or
* model classname is given.
*
* @param array|string|\Illuminate\Database\Eloquent\Model $constraints
* @return void
*/
Blueprint::macro('position', function (array|string|Model $constraints = []) {
/** @var Blueprint $this */
if (is_string($constraints)) {
$constraints = app($constraints);
}
if (!is_array($constraints)) {
$constraints = PositionManifest::constraints($constraints);
} else {
$constraints = collect($constraints)
->push('position')
->unique()
->all();
}
$this->unsignedBigInteger('position');
$this->unique($constraints, $this->getIndexName('unique', ['position']));
});

/**
* Remove the `position` column to the table and drop the unique positions index
*
* @param array|string|\Illuminate\Database\Eloquent\Model $constraints
* @return void
*/
Blueprint::macro('dropPosition', function () {
/** @var Blueprint $this */
$this->dropUnique($this->getIndexName('unique', ['position']));
$this->dropColumn('position');
});
}
}
62 changes: 62 additions & 0 deletions packages/core/src/Base/PositionManifestInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php
namespace Lunar\Base;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

interface PositionManifestInterface
{
/**
* Before model gets saved, check if relevant model attributes has changed
* and position value is greate than zero and unique and if not, set position
* to next greatest value
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function saving(Model $model): void;

/**
* Return the defined constraints from model's positionUniqueConstraints
* property array
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return array
*/
public function constraints(Model $model): array;

/**
* Scope the query to only include given position and constraints
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int $position
* @param array $constraints
* @return void
*/
public function query(Builder $query, int $position, array $constraints = []): void;

/**
* Scope the query to only include given position
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int $position
* @return void
*/
public function queryPosition(Builder $query, int $position): void;

/**
* Scope the query to only include given constraints
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param array|\Illuminate\Support\Collection $constraints
* @return void
*/
public function queryUniqueConstraints(Builder $query, array $constraints): void;

/**
* Regster blueprint macros to allow ease
* ddefinition and removement from `position` columne
* @return void
*/
public static function registerBlueprintMacros(): void;
}
99 changes: 34 additions & 65 deletions packages/core/src/Base/Traits/HasPosition.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,85 +3,54 @@
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Lunar\Facades\PositionManifest;

trait HasPosition
{

final public static function initializeHasPosition(): void
/**
* Initialize the HasPosition trait and register the saving event
*
* @return void
*/
public static function initializeHasPosition(): void
{
static::saving(function (Model $model) {
if (
$model->isDirty($model->positionUniqueConstraints())
|| !(intval($model->position) > 0)
|| $model->query()
->where($model->getKeyName(), '!=', $model->getKey())
->wherePosition(
$model->position,
$model->getAttributes()
)
->exists()
) {
$model->position = $model->query()
->where($model->getKeyName(), '!=', $model->getKey())
->wherePositionUniqueConstraints(
$model->getAttributes()
)
->max('position') + 1;
}
});
static::saving(fn (Model $model) => PositionManifest::saving($model));
}

final public function positionUniqueConstraints(): array
/**
* Scope the query to only include given position
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int $position
* @return void
*/
final public function scopeWherePosition(Builder $query, int $position): void
{
$constraints = ['position'];

if (!property_exists($this, 'positionUniqueConstraints')
|| !is_array($this->positionUniqueConstraints))
{
return $constraints;
}

return array_merge($this->positionUniqueConstraints, $constraints);
}

final public function scopeWherePosition(Builder $query, int $position, array|Collection $constraints = []): void
{
$query
->where('position', $position)
->wherePositionUniqueConstraints($constraints);
PositionManifest::queryPosition($query, $position);
}

/**
* Scope the query to only include given constraints
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param array|\Illuminate\Support\Collection $constraints
* @return void
*/
final public function scopeWherePositionUniqueConstraints(Builder $query, array|Collection $constraints = []): void
{
$constraints = collect($constraints)->except('position');
$modelConstraints = collect($this->positionUniqueConstraints())->reject('position');

if (count($modelConstraints) && !$constraints->hasAny($modelConstraints->toArray())) {
throw new \InvalidArgumentException(
sprintf(
'Position constraints "%s" for "%s" not defined!',
$modelConstraints->diff($constraints)->join('", "', '" and "'),
get_class($this)
)
);
}

$modelConstraints->each(
function ($attribute) use ($query, $constraints) {
if (method_exists($query, Str::camel('scope_' . $attribute))) {
$method = Str::camel($attribute);
} else {
$method = Str::camel('where_' . $attribute);
}
$query->{$method}($constraints[$attribute]);
}
);
PositionManifest::queryUniqueConstraints($query, $constraints);
}

/**
* Scope the query to only include given position and constraints
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int $position
* @param array $constraints
* @return void
*/
public function scopePosition(Builder $query, int $position, ...$constraints): void
{
$query->wherePosition($position, $constraints);
PositionManifest::query($query, $position, $constraints);
}

}
Loading

0 comments on commit 0271fcd

Please sign in to comment.