Skip to content

Commit

Permalink
fix: Remove sushi dependency to avoid pdo requirement include as trait
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-code-labx committed Aug 11, 2024
1 parent 9c467a7 commit 82df1b0
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 7 deletions.
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
],
"require": {
"php": "^8.2",
"calebporzio/sushi": "^2.5",
"knuckleswtf/scribe": "^4.35",
"laravel/framework": "^10|^11",
"laravel/prompts": "^0.1.16",
Expand Down
250 changes: 250 additions & 0 deletions src/Concerns/InteractsWithSushi.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
<?php

declare(strict_types=1);

namespace XtendPackages\RESTPresenter\Concerns;

use Closure;
use DateTime;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\QueryException;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Str;
use ReflectionClass;

trait InteractsWithSushi
{
protected static mixed $sushiConnection;

public static function resolveConnection($connection = null): mixed
{
return static::$sushiConnection;
}

public static function bootInteractsWithSushi(): void
{
$instance = (new static);

$cachePath = $instance->sushiCachePath();
$dataPath = $instance->sushiCacheReferencePath();

$states = [
'cache-file-found-and-up-to-date' => function () use ($cachePath): void {
static::setSqliteConnection($cachePath);
},
'cache-file-not-found-or-stale' => function () use ($cachePath, $dataPath, $instance): void {
static::cacheFileNotFoundOrStale($cachePath, $dataPath, $instance);
},
'no-caching-capabilities' => function () use ($instance): void {
static::setSqliteConnection(':memory:');

$instance->migrate();
},
];

match (true) {
! $instance->sushiShouldCache() => $states['no-caching-capabilities'](),
file_exists($cachePath) && filemtime($dataPath) <= filemtime($cachePath) => $states['cache-file-found-and-up-to-date'](),
file_exists($instance->sushiCacheDirectory()) && is_writable($instance->sushiCacheDirectory()) => $states['cache-file-not-found-or-stale'](),
default => $states['no-caching-capabilities'](),
};
}

public function getRows(): array
{
return $this->rows;
}

public function getSchema(): array
{
return $this->schema ?? [];
}

public function migrate(): void
{
$rows = $this->getRows();
$tableName = $this->getTable();

if (count($rows) > 0) {
$this->createTable($tableName, $rows[0]);
} else {
$this->createTableWithNoData($tableName);
}

foreach (array_chunk($rows, $this->getSushiInsertChunkSize()) ?? [] as $inserts) {
if ($inserts !== []) {
static::insert($inserts);
}
}
}

public function createTable(string $tableName, $firstRow): void
{
$this->createTableSafely($tableName, function ($table) use ($firstRow): void {
// Add the "id" column if it doesn't already exist in the rows.
if ($this->incrementing && ! array_key_exists($this->primaryKey, $firstRow)) {
$table->increments($this->primaryKey);
}

foreach ($firstRow as $column => $value) {

$type = match (true) {
is_int($value) => 'integer',
is_numeric($value) => 'float',
is_string($value) => 'string',
$value instanceof DateTime => 'dateTime',
default => 'string',
};

if ($column === $this->primaryKey && $type === 'integer') {
$table->increments($this->primaryKey);

continue;
}

$schema = $this->getSchema();

$type = $schema[$column] ?? $type;

$table->{$type}($column)->nullable();
}

if ($this->usesTimestamps() && (! in_array('updated_at', array_keys($firstRow)) || ! in_array('created_at', array_keys($firstRow)))) {
$table->timestamps();
}

$this->afterMigrate($table);
});
}

public function createTableWithNoData(string $tableName): void
{
$this->createTableSafely($tableName, function ($table): void {
$schema = $this->getSchema();

if ($this->incrementing && ! in_array($this->primaryKey, array_keys($schema))) {
$table->increments($this->primaryKey);
}

foreach ($schema as $name => $type) {
if ($name === $this->primaryKey && $type === 'integer') {
$table->increments($this->primaryKey);

continue;
}

$table->{$type}($name)->nullable();
}
if (! $this->usesTimestamps()) {
return;
}
if (in_array('updated_at', array_keys($schema)) && in_array('created_at', array_keys($schema))) {
return;
}
$table->timestamps();
});
}

public function usesTimestamps(): bool
{
// Override the Laravel default value of $timestamps = true; Unless otherwise set.
return (new ReflectionClass($this))->getProperty('timestamps')->class === static::class && parent::usesTimestamps();
}

public function getSushiInsertChunkSize(): int
{
return $this->sushiInsertChunkSize ?? 100;
}

public function getConnectionName(): string
{
return static::class;
}

protected static function cacheFileNotFoundOrStale($cachePath, $dataPath, $instance): void
{
file_put_contents($cachePath, '');

static::setSqliteConnection($cachePath);

$instance->migrate();

touch($cachePath, filemtime($dataPath));
}

protected static function setSqliteConnection($database): void
{
$config = [
'driver' => 'sqlite',
'database' => $database,
];

static::$sushiConnection = app(ConnectionFactory::class)->make($config);

app('config')->set('database.connections.'.static::class, $config);
}

protected function sushiCacheReferencePath(): string|false
{
return (new ReflectionClass(static::class))->getFileName();
}

protected function sushiShouldCache(): bool
{
return property_exists(static::class, 'rows');
}

protected function sushiCachePath(): string
{
return implode(DIRECTORY_SEPARATOR, [
$this->sushiCacheDirectory(),
$this->sushiCacheFileName(),
]);
}

protected function sushiCacheFileName(): string
{
return 'sushi'.'-'.Str::kebab(str_replace('\\', '', static::class)).'.sqlite';
}

protected function sushiCacheDirectory(): string
{
return realpath(storage_path('framework/cache'));
}

protected function newRelatedInstance($class): mixed
{
return tap(new $class, function ($instance): void {
if (! $instance->getConnectionName()) {
$instance->setConnection($this->getConnectionResolver()->getDefaultConnection());
}
});
}

protected function afterMigrate(Blueprint $table): void
{
//
}

protected function createTableSafely(string $tableName, Closure $callback): void
{
/** @var \Illuminate\Database\Schema\SQLiteBuilder $schemaBuilder */
$schemaBuilder = static::resolveConnection()->getSchemaBuilder();

try {
$schemaBuilder->create($tableName, $callback);
} catch (QueryException $e) {
if (Str::contains($e->getMessage(), [
'already exists (SQL: create table',
sprintf('table "%s" already exists', $tableName),
])) {
// This error can happen in rare circumstances due to a race condition.
// Concurrent requests may both see the necessary preconditions for
// the table creation, but only one can actually succeed.
return;
}

throw $e;
}
}
}
4 changes: 2 additions & 2 deletions src/Models/Endpoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use InvalidArgumentException;
use Sushi\Sushi;
use XtendPackages\RESTPresenter\Concerns\InteractsWithSushi;

/**
* @property int $id
Expand All @@ -20,7 +20,7 @@
*/
class Endpoint extends Model
{
use Sushi;
use InteractsWithSushi;

/**
* @return array<mixed>
Expand Down
4 changes: 2 additions & 2 deletions src/Models/TestCoverage.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
namespace XtendPackages\RESTPresenter\Models;

use Illuminate\Database\Eloquent\Model;
use Sushi\Sushi;
use XtendPackages\RESTPresenter\Concerns\InteractsWithSushi;

class TestCoverage extends Model
{
use Sushi;
use InteractsWithSushi;

/**
* @var array<array<string, mixed>>
Expand Down
4 changes: 2 additions & 2 deletions src/Models/TestReport.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
namespace XtendPackages\RESTPresenter\Models;

use Illuminate\Database\Eloquent\Model;
use Sushi\Sushi;
use XtendPackages\RESTPresenter\Concerns\InteractsWithSushi;

class TestReport extends Model
{
use Sushi;
use InteractsWithSushi;

/**
* @var array<array<string, mixed>>
Expand Down

0 comments on commit 82df1b0

Please sign in to comment.