diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml
index d6322e59..ff96e40f 100644
--- a/.github/workflows/coding-standards.yml
+++ b/.github/workflows/coding-standards.yml
@@ -13,8 +13,8 @@ on:
jobs:
coding-standards:
name: "Coding Standards"
- uses: "doctrine/.github/.github/workflows/coding-standards.yml@1.3.0"
+ uses: "doctrine/.github/.github/workflows/coding-standards.yml@5.2.0"
with:
- php-version: '8.1'
+ php-version: '8.2'
composer-options: '--prefer-dist --ignore-platform-req=php'
diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 14d6da58..f2cdaae4 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -19,7 +19,6 @@ jobs:
fail-fast: false
matrix:
php-version:
- - "8.1"
- "8.2"
- "8.3"
dependencies:
diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
index 74a86494..9be77f61 100644
--- a/.github/workflows/static-analysis.yml
+++ b/.github/workflows/static-analysis.yml
@@ -12,7 +12,6 @@ jobs:
strategy:
matrix:
php-version:
- - "8.1"
- "8.2"
- "8.3"
diff --git a/.gitignore b/.gitignore
index ff5dc42b..ba8e64dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,7 @@ composer.phar
composer.lock
.DS_Store
.phpcs-cache
-.phpunit.result.cache
+.phpunit.cache
/tests/Stubs/storage/framework/views/*
!/tests/Stubs/storage/framework/views/.gitkeep
diff --git a/README.md b/README.md
index ad8c732e..3846d8a4 100644
--- a/README.md
+++ b/README.md
@@ -43,9 +43,9 @@ or in the docs directory.
Versions
--------
-* Version 3 supports DBAL ^4.0 and ORM ^3.0. See the [upgrade guide](https://laravel-doctrine-orm-official.readthedocs.io/en/latest/upgrade.html) for more information.
-* Version 2 supports Laravel 9 - 11, DBAL ^3.0 and ORM ^2.0.
-* Version 1 supports Laravel 6 - 9, DBAL ^2.0 and ORM ^2.0.
+* Version 3 supports DBAL ^4.0, ORM ^3.0, and PHP 8.2. See the [upgrade guide](https://laravel-doctrine-orm-official.readthedocs.io/en/latest/upgrade.html) for more information.
+* Version 2 supports Laravel 9 - 11, DBAL ^3.0, ORM ^2.0, and PHP ^8.0.
+* Version 1 supports Laravel 6 - 9, DBAL ^2.0, ORM ^2.0, and PHP ^5.5 - ^8.0.
See [documentation in version 2](https://github.com/laravel-doctrine/orm/tree/2.0?tab=readme-ov-file#versions)
License
diff --git a/composer.json b/composer.json
index e8df54b5..55b61197 100644
--- a/composer.json
+++ b/composer.json
@@ -18,7 +18,7 @@
}
],
"require": {
- "php": "^8.1",
+ "php": "^8.2",
"doctrine/dbal": "^3.0 || ^4.0",
"doctrine/orm": "^3.1",
"doctrine/persistence": "^3.3",
@@ -44,9 +44,10 @@
"php-parallel-lint/php-parallel-lint": "^1.4",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-deprecation-rules": "^1.1",
- "phpunit/phpunit": "^9.3",
+ "phpunit/phpunit": "^11.4",
"fakerphp/faker": "^1.23",
- "laravel/framework": "^10.0 || ^11.0"
+ "laravel/framework": "^10.0 || ^11.0",
+ "orchestra/testbench": "^9.5"
},
"conflict": {
"laravel/lumen": "*"
@@ -61,7 +62,10 @@
},
"autoload-dev": {
"psr-4": {
- "LaravelDoctrineTest\\ORM\\": "tests/"
+ "LaravelDoctrineTest\\ORM\\": "tests/",
+ "Workbench\\App\\": "workbench/app/",
+ "Workbench\\Database\\Factories\\": "workbench/database/factories/",
+ "Workbench\\Database\\Seeders\\": "workbench/database/seeders/"
}
},
"suggest": {
@@ -95,6 +99,21 @@
"vendor/bin/phpunit",
"vendor/bin/phpstan analyze src --level 1"
],
- "coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage"
+ "coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage",
+ "post-autoload-dump": [
+ "@clear",
+ "@prepare"
+ ],
+ "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi",
+ "prepare": "@php vendor/bin/testbench package:discover --ansi",
+ "build": "@php vendor/bin/testbench workbench:build --ansi",
+ "serve": [
+ "Composer\\Config::disableProcessTimeout",
+ "@build",
+ "@php vendor/bin/testbench serve --ansi"
+ ],
+ "lint": [
+ "@php vendor/bin/phpstan analyse --verbose --ansi"
+ ]
}
}
diff --git a/config/doctrine.php b/config/doctrine.php
index e99d9e39..ea89777f 100644
--- a/config/doctrine.php
+++ b/config/doctrine.php
@@ -104,7 +104,6 @@
|
*/
'extensions' => [
- //LaravelDoctrine\ORM\Extensions\TablePrefix\TablePrefixExtension::class,
//LaravelDoctrine\Extensions\Timestamps\TimestampableExtension::class,
//LaravelDoctrine\Extensions\SoftDeletes\SoftDeleteableExtension::class,
//LaravelDoctrine\Extensions\Sluggable\SluggableExtension::class,
diff --git a/phpunit.xml b/phpunit.xml
index 493fd4a7..165b475a 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,24 +1,26 @@
-
-
-
- src/
-
-
+ cacheDirectory=".phpunit.cache"
+ executionOrder="depends,defects"
+ shortenArraysForExportThreshold="10"
+ requireCoverageMetadata="false"
+ beStrictAboutCoverageMetadata="false"
+ beStrictAboutOutputDuringTests="true"
+ displayDetailsOnPhpunitDeprecations="true"
+ failOnPhpunitDeprecation="true"
+ failOnRisky="true"
+ failOnWarning="true">
-
- ./tests/
+
+ tests
+
+
+
+ src
+
+
diff --git a/src/DoctrineServiceProvider.php b/src/DoctrineServiceProvider.php
index 69dfbcad..e048bdfd 100644
--- a/src/DoctrineServiceProvider.php
+++ b/src/DoctrineServiceProvider.php
@@ -10,10 +10,8 @@
use Doctrine\ORM\Proxy\Autoloader;
use Doctrine\Persistence\ManagerRegistry;
use Illuminate\Contracts\Container\Container;
-use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Notifications\ChannelManager;
use Illuminate\Support\ServiceProvider;
-use Illuminate\Support\Str;
use InvalidArgumentException;
use LaravelDoctrine\ORM\Auth\DoctrineUserProvider;
use LaravelDoctrine\ORM\Configuration\Cache\CacheManager;
@@ -37,8 +35,8 @@
use function assert;
use function class_exists;
+use function config;
use function config_path;
-use function property_exists;
class DoctrineServiceProvider extends ServiceProvider
{
@@ -50,13 +48,9 @@ public function boot(): void
$this->extendAuthManager();
$this->extendNotificationChannel();
- if (! $this->isLumen()) {
- $this->publishes([
- $this->getConfigPath() => config_path('doctrine.php'),
- ], 'config');
- }
-
- $this->ensureValidatorIsUsable();
+ $this->publishes([
+ $this->getConfigPath() => config_path('doctrine.php'),
+ ], 'config');
}
/**
@@ -83,28 +77,6 @@ public function register(): void
$this->registerPresenceVerifierProvider();
}
- protected function ensureValidatorIsUsable(): void
- {
- if (! $this->isLumen()) {
- return;
- }
-
- assert(property_exists($this->app, 'availableBindings'));
-
- if ($this->shouldRegisterDoctrinePresenceValidator()) {
- // due to weirdness the default presence verifier overrides one set by a service provider
- // so remove them so we can re add our implementation later
- unset($this->app->availableBindings['validator']);
- unset($this->app->availableBindings[ValidationFactory::class]);
- } else {
- // resolve the db,
- // this makes `isset($this->app['db']) == true`
- // which is required to set the presence verifier
- // in the default ValidationServiceProvider implementation
- $this->app['db'];
- }
- }
-
/**
* Merge config
*/
@@ -114,14 +86,6 @@ protected function mergeConfig(): void
$this->getConfigPath(),
'doctrine',
);
-
- if (! $this->isLumen()) {
- return;
- }
-
- $this->app->configure('cache');
- $this->app->configure('database');
- $this->app->configure('doctrine');
}
/**
@@ -232,15 +196,7 @@ protected function registerExtensions(): void
*/
protected function registerPresenceVerifierProvider(): void
{
- if ($this->isLumen()) {
- $this->app->singleton('validator', function () {
- $this->app->register(PresenceVerifierProvider::class);
-
- return $this->app->make('validator');
- });
- } else {
- $this->app->register(PresenceVerifierProvider::class);
- }
+ $this->app->register(PresenceVerifierProvider::class);
}
/**
@@ -279,7 +235,7 @@ protected function extendAuthManager(): void
/**
* Boots the extension manager at the appropriate time depending on if the app
- * is running as Laravel HTTP, Lumen HTTP or in a console environment
+ * is running as Laravel HTTP or in a console environment
*/
protected function bootExtensionManager(): void
{
@@ -354,13 +310,8 @@ protected function registerConsoleCommands(): void
]);
}
- protected function isLumen(): bool
- {
- return Str::contains($this->app->version(), 'Lumen');
- }
-
protected function shouldRegisterDoctrinePresenceValidator(): bool
{
- return $this->app['config']->get('doctrine.doctrine_presence_verifier', true);
+ return config('doctrine.doctrine_presence_verifier', true);
}
}
diff --git a/src/IlluminateRegistry.php b/src/IlluminateRegistry.php
index a4eae915..45663967 100644
--- a/src/IlluminateRegistry.php
+++ b/src/IlluminateRegistry.php
@@ -301,8 +301,10 @@ public function getRepository(string $persistentObject, string|null $persistentM
* Gets the object manager associated with a given class.
*
* @param class-string $className A persistent object class name.
+ *
+ * @return ($throwExceptionIfNotFound is true ? ObjectManager : ObjectManager|null)
*/
- public function getManagerForClass(string $className): ObjectManager|null
+ public function getManagerForClass(string $className, bool $throwExceptionIfNotFound = false): ObjectManager|null
{
// Check for namespace alias
if (strpos($className, ':') !== false) {
@@ -335,7 +337,11 @@ public function getManagerForClass(string $className): ObjectManager|null
}
}
- throw new RuntimeException('No manager found for class ' . $className);
+ if ($throwExceptionIfNotFound) {
+ throw new RuntimeException('No manager found for class ' . $className);
+ }
+
+ return null;
}
/**
diff --git a/src/Testing/Concerns/InteractsWithEntities.php b/src/Testing/Concerns/InteractsWithEntities.php
deleted file mode 100644
index ae980130..00000000
--- a/src/Testing/Concerns/InteractsWithEntities.php
+++ /dev/null
@@ -1,102 +0,0 @@
-entityManager()->find($class, $id);
-
- Assert::assertNotNull($entity, 'A [' . $class . '] entity was not found by id: ' . print_r($id, true));
-
- return $entity;
- }
-
- public function entityDoesNotExist(string $class, mixed $id): void
- {
- Assert::assertNull(
- $this->entityManager()->find($class, $id),
- 'A [' . $class . '] entity was found by id: ' . print_r($id, true),
- );
- }
-
- /**
- * @param mixed[] $criteria
- *
- * @return mixed[]
- */
- public function entitiesMatch(string $class, array $criteria, int|null $count = null): mixed
- {
- $entities = $this->entityManager()->getRepository($class)->findBy($criteria);
-
- Assert::assertNotEmpty($entities, 'No [' . $class . '] entities were found with the given criteria: ' . $this->outputCriteria($criteria));
-
- if ($count !== null) {
- Assert::assertCount(
- $count,
- $entities,
- 'Expected to find ' . $count . ' [' . $class . '] entities, but found ' . count($entities) .
- ' with the given criteria: ' . $this->outputCriteria($criteria),
- );
- }
-
- return $entities;
- }
-
- /** @param mixed[] $criteria */
- public function noEntitiesMatch(string $class, array $criteria): void
- {
- Assert::assertEmpty(
- $this->entityManager()->getRepository($class)->findBy($criteria),
- 'Some [' . $class . '] entities were found with the given criteria: ' . $this->outputCriteria($criteria),
- );
- }
-
- /**
- * Replaces entities with their ids in the criteria array and print_r them
- *
- * @param mixed[] $criteria
- */
- private function outputCriteria(array $criteria): string
- {
- $criteria = collect($criteria)->map(function ($value) {
- if (! is_object($value)) {
- return $value;
- }
-
- $unityOfWork = $this->entityManager()->getUnitOfWork();
- if ($unityOfWork->isInIdentityMap($value)) {
- return $unityOfWork->getEntityIdentifier($value);
- }
-
- return $value;
- })->all();
-
- return print_r($criteria, true);
- }
-
- protected function entityManager(): mixed
- {
- if (! isset($this->app)) {
- Assert::markTestSkipped(
- 'Tests that interact with entities through Doctrine need to have Laravel\'s Application object.' . PHP_EOL .
- 'Please extend Laravel\'s TestCase to use this trait.',
- );
- }
-
- return $this->app->make('em');
- }
-}
diff --git a/testbench.yaml b/testbench.yaml
new file mode 100644
index 00000000..d5e9cf31
--- /dev/null
+++ b/testbench.yaml
@@ -0,0 +1,20 @@
+providers:
+ - LaravelDoctrine\ORM\DoctrineServiceProvider
+
+workbench:
+ start: '/'
+ install: true
+ health: false
+ discovers:
+ web: fale
+ api: false
+ commands: false
+ components: false
+ views: false
+ build:
+ - asset-publish
+ - create-sqlite-db
+ - db-wipe
+ assets:
+ - laravel-assets
+ sync: []
diff --git a/tests/Assets/Extensions/ExtensionMock.php b/tests/Assets/Extensions/ExtensionMock.php
index c84350de..358fdd85 100644
--- a/tests/Assets/Extensions/ExtensionMock.php
+++ b/tests/Assets/Extensions/ExtensionMock.php
@@ -14,7 +14,7 @@ class ExtensionMock implements Extension
public function addSubscribers(EventManager $manager, EntityManagerInterface $em): void
{
// Confirm it gets called
- (new ExtensionManagerTest())->assertTrue(true);
+ (new ExtensionManagerTest('test'))->assertTrue(true);
}
/** @return mixed[] */
diff --git a/tests/Assets/MyDoctrineExtender.php b/tests/Assets/MyDoctrineExtender.php
index 5da39a47..1833d9ba 100644
--- a/tests/Assets/MyDoctrineExtender.php
+++ b/tests/Assets/MyDoctrineExtender.php
@@ -14,6 +14,6 @@ class MyDoctrineExtender implements DoctrineExtender
{
public function extend(Configuration $configuration, Connection $connection, EventManager $eventManager): void
{
- (new DoctrineManagerTest())->assertExtendedCorrectly($configuration, $connection, $eventManager);
+ (new DoctrineManagerTest('test'))->assertExtendedCorrectly($configuration, $connection, $eventManager);
}
}
diff --git a/tests/Feature/Configuration/Cache/ApcCacheProviderTest.php b/tests/Feature/Configuration/Cache/ApcCacheProviderTest.php
index f093f740..58273b72 100644
--- a/tests/Feature/Configuration/Cache/ApcCacheProviderTest.php
+++ b/tests/Feature/Configuration/Cache/ApcCacheProviderTest.php
@@ -10,7 +10,7 @@
use Mockery as m;
use Psr\Cache\CacheItemPoolInterface;
-class ApcCacheProviderTest extends CacheProviderTest
+class ApcCacheProviderTest extends CacheProvider
{
public function getProvider(): mixed
{
diff --git a/tests/Feature/Configuration/Cache/ArrayCacheProviderTest.php b/tests/Feature/Configuration/Cache/ArrayCacheProviderTest.php
index 2b15cfcd..51a8f364 100644
--- a/tests/Feature/Configuration/Cache/ArrayCacheProviderTest.php
+++ b/tests/Feature/Configuration/Cache/ArrayCacheProviderTest.php
@@ -7,7 +7,7 @@
use LaravelDoctrine\ORM\Configuration\Cache\ArrayCacheProvider;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
-class ArrayCacheProviderTest extends CacheProviderTest
+class ArrayCacheProviderTest extends CacheProvider
{
public function getProvider(): mixed
{
diff --git a/tests/Feature/Configuration/Cache/CacheManagerTest.php b/tests/Feature/Configuration/Cache/CacheManagerTest.php
index 0cc64c33..7405dbbf 100644
--- a/tests/Feature/Configuration/Cache/CacheManagerTest.php
+++ b/tests/Feature/Configuration/Cache/CacheManagerTest.php
@@ -19,18 +19,18 @@ class CacheManagerTest extends TestCase
{
protected CacheManager $manager;
- protected Container $app;
+ protected Container $testApp;
protected Repository $config;
protected function setUp(): void
{
- $this->app = m::mock(Container::class);
- $this->app->shouldReceive('make')->andReturn(m::self());
- $this->app->shouldReceive('get')->with('doctrine.cache.default', 'array')->andReturn('array');
+ $this->testApp = m::mock(Container::class);
+ $this->testApp->shouldReceive('make')->andReturn(m::self());
+ $this->testApp->shouldReceive('get')->with('doctrine.cache.default', 'array')->andReturn('array');
$this->manager = new CacheManager(
- $this->app,
+ $this->testApp,
);
parent::setUp();
@@ -38,7 +38,7 @@ protected function setUp(): void
public function testDriverReturnsTheDefaultDriver(): void
{
- $this->app->shouldReceive('resolve')->andReturn(new ArrayCacheProvider());
+ $this->testApp->shouldReceive('resolve')->andReturn(new ArrayCacheProvider());
$this->assertInstanceOf(ArrayCacheProvider::class, $this->manager->driver());
$this->assertInstanceOf(ArrayAdapter::class, $this->manager->driver()->resolve());
@@ -49,7 +49,7 @@ public function testDriverCanReturnAGivenDriver(): void
$config = m::mock(Repository::class);
$app = m::mock(Application::class);
- $this->app->shouldReceive('resolve')->andReturn(new FileCacheProvider(
+ $this->testApp->shouldReceive('resolve')->andReturn(new FileCacheProvider(
$config,
$app,
));
diff --git a/tests/Feature/Configuration/Cache/CacheProviderTest.php b/tests/Feature/Configuration/Cache/CacheProvider.php
similarity index 91%
rename from tests/Feature/Configuration/Cache/CacheProviderTest.php
rename to tests/Feature/Configuration/Cache/CacheProvider.php
index d3763c14..98d7e841 100644
--- a/tests/Feature/Configuration/Cache/CacheProviderTest.php
+++ b/tests/Feature/Configuration/Cache/CacheProvider.php
@@ -7,7 +7,7 @@
use LaravelDoctrineTest\ORM\TestCase;
use Mockery;
-abstract class CacheProviderTest extends TestCase
+abstract class CacheProvider extends TestCase
{
abstract public function getProvider(): mixed;
diff --git a/tests/Feature/Configuration/Cache/FileCacheProviderTest.php b/tests/Feature/Configuration/Cache/FileCacheProviderTest.php
index 9bc16e20..de844bf1 100644
--- a/tests/Feature/Configuration/Cache/FileCacheProviderTest.php
+++ b/tests/Feature/Configuration/Cache/FileCacheProviderTest.php
@@ -9,13 +9,16 @@
use Mockery as m;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
-class FileCacheProviderTest extends CacheProviderTest
+class FileCacheProviderTest extends CacheProvider
{
public function getProvider(): mixed
{
$config = m::mock(Repository::class);
$config->shouldReceive('get')
- ->with('cache.stores.file.path', '/storage/framework/cache')
+ ->with(
+ 'cache.stores.file.path',
+ $this->applicationBasePath() . '/storage/framework/cache',
+ )
->once()
->andReturn('/tmp');
diff --git a/tests/Feature/Configuration/Cache/MemcachedCacheProviderTest.php b/tests/Feature/Configuration/Cache/MemcachedCacheProviderTest.php
index c130b09e..7ef640a0 100644
--- a/tests/Feature/Configuration/Cache/MemcachedCacheProviderTest.php
+++ b/tests/Feature/Configuration/Cache/MemcachedCacheProviderTest.php
@@ -10,7 +10,7 @@
use Mockery as m;
use Psr\Cache\CacheItemPoolInterface;
-class MemcachedCacheProviderTest extends CacheProviderTest
+class MemcachedCacheProviderTest extends CacheProvider
{
public function getProvider(): mixed
{
diff --git a/tests/Feature/Configuration/Cache/PhpFileCacheProviderTest.php b/tests/Feature/Configuration/Cache/PhpFileCacheProviderTest.php
index 7fbce6c3..bc305183 100644
--- a/tests/Feature/Configuration/Cache/PhpFileCacheProviderTest.php
+++ b/tests/Feature/Configuration/Cache/PhpFileCacheProviderTest.php
@@ -9,13 +9,16 @@
use Mockery as m;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
-class PhpFileCacheProviderTest extends CacheProviderTest
+class PhpFileCacheProviderTest extends CacheProvider
{
public function getProvider(): mixed
{
$config = m::mock(Repository::class);
$config->shouldReceive('get')
- ->with('cache.stores.file.path', '/storage/framework/cache')
+ ->with(
+ 'cache.stores.file.path',
+ $this->applicationBasePath() . '/storage/framework/cache',
+ )
->once()
->andReturn('/tmp');
diff --git a/tests/Feature/Configuration/Cache/RedisCacheProviderTest.php b/tests/Feature/Configuration/Cache/RedisCacheProviderTest.php
index a2a8fa03..fed819f0 100644
--- a/tests/Feature/Configuration/Cache/RedisCacheProviderTest.php
+++ b/tests/Feature/Configuration/Cache/RedisCacheProviderTest.php
@@ -10,7 +10,7 @@
use Mockery as m;
use Psr\Cache\CacheItemPoolInterface;
-class RedisCacheProviderTest extends CacheProviderTest
+class RedisCacheProviderTest extends CacheProvider
{
public function getProvider(): mixed
{
diff --git a/tests/Feature/Configuration/Connections/ConnectionManagerTest.php b/tests/Feature/Configuration/Connections/ConnectionManagerTest.php
index 896c7dd3..332ee3c4 100644
--- a/tests/Feature/Configuration/Connections/ConnectionManagerTest.php
+++ b/tests/Feature/Configuration/Connections/ConnectionManagerTest.php
@@ -19,20 +19,20 @@ class ConnectionManagerTest extends TestCase
{
protected ConnectionManager $manager;
- protected Container $app;
+ protected Container $testApp;
protected Repository $config;
protected function setUp(): void
{
- $this->app = m::mock(Container::class);
- $this->app->shouldReceive('make')->andReturn(m::self());
+ $this->testApp = m::mock(Container::class);
+ $this->testApp->shouldReceive('make')->andReturn(m::self());
$this->config = m::mock(Repository::class);
$this->config->shouldReceive('get');
$this->manager = new ConnectionManager(
- $this->app,
+ $this->testApp,
);
parent::setUp();
@@ -40,7 +40,7 @@ protected function setUp(): void
public function testDriverReturnsTheDefaultDriver(): void
{
- $this->app->shouldReceive('resolve')->andReturn(
+ $this->testApp->shouldReceive('resolve')->andReturn(
(new MysqlConnection($this->config))->resolve(),
);
@@ -50,7 +50,7 @@ public function testDriverReturnsTheDefaultDriver(): void
public function testDriverCanReturnAGivenDriver(): void
{
- $this->app->shouldReceive('resolve')->andReturn(
+ $this->testApp->shouldReceive('resolve')->andReturn(
(new SqliteConnection($this->config))->resolve(),
);
diff --git a/tests/Feature/Configuration/Connections/PrimaryReadReplicaConnectionTest.php b/tests/Feature/Configuration/Connections/PrimaryReadReplicaConnectionTest.php
index 074f42c8..4b964228 100644
--- a/tests/Feature/Configuration/Connections/PrimaryReadReplicaConnectionTest.php
+++ b/tests/Feature/Configuration/Connections/PrimaryReadReplicaConnectionTest.php
@@ -9,6 +9,7 @@
use LaravelDoctrine\ORM\Configuration\Connections\PrimaryReadReplicaConnection;
use LaravelDoctrineTest\ORM\TestCase;
use Mockery as m;
+use PHPUnit\Framework\Attributes\DataProvider;
use function class_exists;
@@ -31,57 +32,57 @@ protected function setUp(): void
*
* @return mixed[]
*/
- public function getPrimaryReplicaConnectionData(): array
+ public static function getPrimaryReplicaConnectionData(): array
{
$out = [];
// Case #0. Simple valid configuration with mysql base settings.
$out[] = [
- $this->getResolvedMysqlConfig(),
- $this->getInputConfigwithArrayOfReplicasInReadKey(),
- $this->getExpectedConfig(),
+ self::getResolvedMysqlConfig(),
+ self::getInputConfigwithArrayOfReplicasInReadKey(),
+ self::getExpectedConfig(),
];
// Case #1. Configuration is only set in the read/write nodes.
$out[] = [
['driver' => 'pdo_mysql'],
- $this->getNodesInputConfig(),
- $this->getNodesExpectedConfig(),
+ self::getNodesInputConfig(),
+ self::getNodesExpectedConfig(),
];
// Case #2. Simple valid configuration with oracle base settings.
$out[] = [
- $this->getResolvedOracleConfig(),
- $this->getInputConfigwithArrayOfReplicasInReadKey(),
- $this->getOracleExpectedConfig(),
+ self::getResolvedOracleConfig(),
+ self::getInputConfigwithArrayOfReplicasInReadKey(),
+ self::getOracleExpectedConfig(),
];
// Case #3. Simple valid configuration with pgqsql base settings.
$out[] = [
- $this->getResolvedPgqsqlConfig(),
- $this->getInputConfigwithArrayOfReplicasInReadKey(),
- $this->getPgsqlExpectedConfig(),
+ self::getResolvedPgqsqlConfig(),
+ self::getInputConfigwithArrayOfReplicasInReadKey(),
+ self::getPgsqlExpectedConfig(),
];
// Case #4. Simple valid configuration with sqlite base settings.
$out[] = [
- $this->getResolvedSqliteConfig(),
- $this->getSqliteInputConfig(),
- $this->getSqliteExpectedConfig(),
+ self::getResolvedSqliteConfig(),
+ self::getSqliteInputConfig(),
+ self::getSqliteExpectedConfig(),
];
// Case #5. Valid configuration as with 1 replica 'read' entry and plain-text host
$out[] = [
- $this->getResolvedMysqlConfig(),
- $this->getInputConfigWithPlainTextHostValue(),
- $this->getExpectedConfigForCase5(),
+ self::getResolvedMysqlConfig(),
+ self::getInputConfigWithPlainTextHostValue(),
+ self::getExpectedConfigForCase5(),
];
// Case #6. Valid configuration as with 1 replica config 'read' entry and array of hosts in 'host' key
$out[] = [
- $this->getResolvedMysqlConfig(),
- $this->getInputConfigWithArrayAsHostValue(),
- $this->getExpectedConfigForCase6(),
+ self::getResolvedMysqlConfig(),
+ self::getInputConfigWithArrayAsHostValue(),
+ self::getExpectedConfigForCase6(),
];
return $out;
@@ -93,9 +94,8 @@ public function getPrimaryReplicaConnectionData(): array
* @param mixed[] $resolvedBaseSettings
* @param mixed[] $settings
* @param mixed[] $expectedOutput
- *
- * @dataProvider getPrimaryReplicaConnectionData
*/
+ #[DataProvider('getPrimaryReplicaConnectionData')]
public function testPrimaryReplicaConnection(array $resolvedBaseSettings, array $settings, array $expectedOutput): void
{
$this->assertEquals(
@@ -109,7 +109,7 @@ public function testPrimaryReplicaConnection(array $resolvedBaseSettings, array
*
* @return mixed[]
*/
- private function getInputConfigwithArrayOfReplicasInReadKey(): array
+ private static function getInputConfigwithArrayOfReplicasInReadKey(): array
{
return [
'driver' => 'mysql',
@@ -147,7 +147,7 @@ private function getInputConfigwithArrayOfReplicasInReadKey(): array
}
/** @return mixed[] */
- private function getInputConfigWithPlainTextHostValue(): array
+ private static function getInputConfigWithPlainTextHostValue(): array
{
return [
'driver' => 'mysql',
@@ -180,7 +180,7 @@ private function getInputConfigWithPlainTextHostValue(): array
}
/** @return mixed[] */
- private function getInputConfigWithArrayAsHostValue(): array
+ private static function getInputConfigWithArrayAsHostValue(): array
{
return [
'driver' => 'mysql',
@@ -217,7 +217,7 @@ private function getInputConfigWithArrayAsHostValue(): array
*
* @return mixed[]
*/
- private function getExpectedConfig(): array
+ private static function getExpectedConfig(): array
{
return [
'wrapperClass' => PrimaryReadReplicaDoctrineWrapper::class,
@@ -267,7 +267,7 @@ private function getExpectedConfig(): array
*
* @return mixed[]
*/
- private function getExpectedConfigForCase5(): array
+ private static function getExpectedConfigForCase5(): array
{
return [
'wrapperClass' => PrimaryReadReplicaDoctrineWrapper::class,
@@ -307,7 +307,7 @@ private function getExpectedConfigForCase5(): array
*
* @return mixed[]
*/
- private function getExpectedConfigForCase6(): array
+ private static function getExpectedConfigForCase6(): array
{
return [
'wrapperClass' => PrimaryReadReplicaDoctrineWrapper::class,
@@ -357,7 +357,7 @@ private function getExpectedConfigForCase6(): array
*
* @return mixed[]
*/
- private function getNodesInputConfig(): array
+ private static function getNodesInputConfig(): array
{
return [
'write' => [
@@ -391,7 +391,7 @@ private function getNodesInputConfig(): array
*
* @return mixed[]
*/
- private function getNodesExpectedConfig(): array
+ private static function getNodesExpectedConfig(): array
{
return [
'wrapperClass' => PrimaryReadReplicaDoctrineWrapper::class,
@@ -427,9 +427,9 @@ private function getNodesExpectedConfig(): array
*
* @return mixed[]
*/
- private function getOracleExpectedConfig(): array
+ private static function getOracleExpectedConfig(): array
{
- $expectedConfigOracle = $this->getNodesExpectedConfig();
+ $expectedConfigOracle = self::getNodesExpectedConfig();
$expectedConfigOracle['driver'] = 'oci8';
$expectedConfigOracle['primary']['user'] = 'homestead1';
$expectedConfigOracle['serverVersion'] = '5.8';
@@ -447,9 +447,9 @@ private function getOracleExpectedConfig(): array
*
* @return mixed[]
*/
- private function getPgsqlExpectedConfig(): array
+ private static function getPgsqlExpectedConfig(): array
{
- $expectedConfigPgsql = $this->getNodesExpectedConfig();
+ $expectedConfigPgsql = self::getNodesExpectedConfig();
$expectedConfigPgsql['driver'] = 'pgsql';
$expectedConfigPgsql['primary']['user'] = 'homestead1';
$expectedConfigPgsql['primary']['sslmode'] = 'sslmode';
@@ -470,7 +470,7 @@ private function getPgsqlExpectedConfig(): array
*
* @return mixed[]
*/
- private function getSqliteExpectedConfig(): array
+ private static function getSqliteExpectedConfig(): array
{
return [
'wrapperClass' => PrimaryReadReplicaDoctrineWrapper::class,
@@ -512,9 +512,9 @@ private function getSqliteExpectedConfig(): array
*
* @return mixed[]
*/
- private function getSqliteInputConfig(): array
+ private static function getSqliteInputConfig(): array
{
- $inputConfigSqlite = $this->getInputConfigwithArrayOfReplicasInReadKey();
+ $inputConfigSqlite = self::getInputConfigwithArrayOfReplicasInReadKey();
unset($inputConfigSqlite['read'][0]['database']);
unset($inputConfigSqlite['read'][1]['database']);
unset($inputConfigSqlite['write']['database']);
@@ -527,7 +527,7 @@ private function getSqliteInputConfig(): array
*
* @return mixed[]
*/
- private function getResolvedMysqlConfig(): array
+ private static function getResolvedMysqlConfig(): array
{
return [
'driver' => 'pdo_mysql',
@@ -547,7 +547,7 @@ private function getResolvedMysqlConfig(): array
*
* @return mixed[]
*/
- private function getResolvedOracleConfig(): array
+ private static function getResolvedOracleConfig(): array
{
return [
'driver' => 'oci8',
@@ -564,7 +564,7 @@ private function getResolvedOracleConfig(): array
*
* @return mixed[]
*/
- private function getResolvedSqliteConfig(): array
+ private static function getResolvedSqliteConfig(): array
{
return [
'driver' => 'pdo_sqlite',
@@ -580,7 +580,7 @@ private function getResolvedSqliteConfig(): array
*
* @return mixed[]
*/
- private function getResolvedPgqsqlConfig(): array
+ private static function getResolvedPgqsqlConfig(): array
{
return [
'driver' => 'pgsql',
diff --git a/tests/Feature/Configuration/MetaData/MetaDataManagerTest.php b/tests/Feature/Configuration/MetaData/MetaDataManagerTest.php
index 11490e47..67fb2db0 100644
--- a/tests/Feature/Configuration/MetaData/MetaDataManagerTest.php
+++ b/tests/Feature/Configuration/MetaData/MetaDataManagerTest.php
@@ -15,15 +15,15 @@ class MetaDataManagerTest extends TestCase
{
protected MetaDataManager $manager;
- protected Container $app;
+ protected Container $testApp;
protected function setUp(): void
{
- $this->app = m::mock(Container::class);
- $this->app->shouldReceive('make')->andReturn(m::self());
+ $this->testApp = m::mock(Container::class);
+ $this->testApp->shouldReceive('make')->andReturn(m::self());
$this->manager = new MetaDataManager(
- $this->app,
+ $this->testApp,
);
parent::setUp();
@@ -31,7 +31,7 @@ protected function setUp(): void
public function testDriverReturnsTheDefaultDriver(): void
{
- $this->app->shouldReceive('resolve')->andReturn(new XmlDriver('locator', '.xml'));
+ $this->testApp->shouldReceive('resolve')->andReturn(new XmlDriver('locator', '.xml'));
$this->assertInstanceOf(XmlDriver::class, $this->manager->driver());
}
diff --git a/tests/Feature/DoctrineServiceProviderCustomFunctionsTest.php b/tests/Feature/DoctrineServiceProviderCustomFunctionsTest.php
new file mode 100644
index 00000000..87f7bfc2
--- /dev/null
+++ b/tests/Feature/DoctrineServiceProviderCustomFunctionsTest.php
@@ -0,0 +1,37 @@
+app->get('registry');
+
+ $this->assertInstanceOf(
+ ManagerRegistry::class,
+ $registry,
+ );
+ }
+}
diff --git a/tests/Feature/DoctrineServiceProviderExtensionTest.php b/tests/Feature/DoctrineServiceProviderExtensionTest.php
new file mode 100644
index 00000000..48b6cb5a
--- /dev/null
+++ b/tests/Feature/DoctrineServiceProviderExtensionTest.php
@@ -0,0 +1,32 @@
+app->get('registry');
+
+ $this->assertTrue(true);
+ }
+}
diff --git a/tests/Feature/DoctrineServiceProviderInvalidExtensionTest.php b/tests/Feature/DoctrineServiceProviderInvalidExtensionTest.php
new file mode 100644
index 00000000..b7832330
--- /dev/null
+++ b/tests/Feature/DoctrineServiceProviderInvalidExtensionTest.php
@@ -0,0 +1,34 @@
+set('doctrine.extensions', ['invalid' => 'InvalalidExtension']);
+ });
+ }
+
+ public function testInvalidException(): void
+ {
+ $this->expectException(ExtensionNotFound::class);
+
+ $registry = $this->app->get('registry');
+ }
+}
diff --git a/tests/Feature/DoctrineServiceProviderTest.php b/tests/Feature/DoctrineServiceProviderTest.php
new file mode 100644
index 00000000..0c013cfd
--- /dev/null
+++ b/tests/Feature/DoctrineServiceProviderTest.php
@@ -0,0 +1,40 @@
+app->get('registry');
+
+ $this->assertInstanceOf(
+ ManagerRegistry::class,
+ $registry,
+ );
+ }
+
+ public function testEntityManagerSingleton(): void
+ {
+ $em1 = $this->app->get('em');
+ $em2 = $this->app->get('em');
+
+ $this->assertSame($em1, $em2);
+ }
+
+ public function testMetaDataFactory(): void
+ {
+ $metaDataFactory = $this->app->get(ClassMetadataFactory::class);
+
+ $this->assertInstanceOf(
+ ClassMetadataFactory::class,
+ $metaDataFactory,
+ );
+ }
+}
diff --git a/tests/Feature/EntityManagerFactoryTest.php b/tests/Feature/EntityManagerFactoryTest.php
index b26ec66d..af9434a2 100644
--- a/tests/Feature/EntityManagerFactoryTest.php
+++ b/tests/Feature/EntityManagerFactoryTest.php
@@ -38,6 +38,7 @@
use LaravelDoctrineTest\ORM\TestCase;
use Mockery as m;
use Mockery\Mock;
+use PHPUnit\Framework\Attributes\DataProvider;
use Psr\Cache\CacheItemPoolInterface;
use ReflectionException;
use ReflectionObject;
@@ -1008,15 +1009,15 @@ protected function enableLaravelNamingStrategy(): void
*
* @return mixed[]
*/
- public function getTestPrimaryReadReplicaConnectionData(): array
+ public static function getTestPrimaryReadReplicaConnectionData(): array
{
$out = [];
// Case #0. Simple valid configuration, everything should go well.
- $out[] = [$this->getDummyBaseInputConfig()];
+ $out[] = [self::getDummyBaseInputConfig()];
//Case #1. No read DBs set.
- $inputConfig = $this->getDummyBaseInputConfig();
+ $inputConfig = self::getDummyBaseInputConfig();
unset($inputConfig['read']);
$out[] = [
@@ -1026,7 +1027,7 @@ public function getTestPrimaryReadReplicaConnectionData(): array
];
//Case #2. 'read' isn't an array
- $inputConfig = $this->getDummyBaseInputConfig();
+ $inputConfig = self::getDummyBaseInputConfig();
$inputConfig['read'] = 'test';
$out[] = [
@@ -1036,7 +1037,7 @@ public function getTestPrimaryReadReplicaConnectionData(): array
];
//Case #3. 'read' has non array entries.
- $inputConfig = $this->getDummyBaseInputConfig();
+ $inputConfig = self::getDummyBaseInputConfig();
$inputConfig['read'][] = 'test';
$out[] = [
@@ -1046,7 +1047,7 @@ public function getTestPrimaryReadReplicaConnectionData(): array
];
//Case #4. 'read' has empty entries.
- $inputConfig = $this->getDummyBaseInputConfig();
+ $inputConfig = self::getDummyBaseInputConfig();
$inputConfig['read'][] = [];
$out[] = [
@@ -1056,7 +1057,7 @@ public function getTestPrimaryReadReplicaConnectionData(): array
];
//Case #5. 'read' has empty first entry. (reported by maxbrokman.)
- $inputConfig = $this->getDummyBaseInputConfig();
+ $inputConfig = self::getDummyBaseInputConfig();
$inputConfig['read'][0] = [];
$out[] = [
@@ -1072,9 +1073,8 @@ public function getTestPrimaryReadReplicaConnectionData(): array
* Check if config is handled correctly.
*
* @param mixed[] $inputConfig
- *
- * @dataProvider getTestPrimaryReadReplicaConnectionData
*/
+ #[DataProvider('getTestPrimaryReadReplicaConnectionData')]
public function testPrimaryReadReplicaConnection(
array $inputConfig,
string $expectedException = '',
@@ -1134,7 +1134,7 @@ protected function tearDown(): void
*
* @return mixed[]
*/
- private function getDummyBaseInputConfig(): array
+ private static function getDummyBaseInputConfig(): array
{
return [
'driver' => 'mysql',
diff --git a/tests/Feature/IlluminateRegistryTest.php b/tests/Feature/IlluminateRegistryTest.php
index 79ef0fc9..5385a106 100644
--- a/tests/Feature/IlluminateRegistryTest.php
+++ b/tests/Feature/IlluminateRegistryTest.php
@@ -485,6 +485,37 @@ public function testGetManagerForClassWithNamespace(): void
$this->assertEquals($entityManager, $this->registry->getManagerForClass('Alias:Scientist'));
}
+ public function testGetManagerForClassReturnsNullWhenNotFound(): void
+ {
+ $this->container->shouldReceive('singleton');
+ $this->registry->addManager('default');
+
+ $entityManager = m::mock(EntityManagerInterface::class);
+ $this->container->shouldReceive('make')
+ ->with('doctrine.managers.default')
+ ->andReturn($entityManager);
+
+ $metadataFactory = m::mock(ClassMetadataFactory::class);
+ $metadataFactory->shouldReceive('isTransient')
+ ->with('LaravelDoctrineTest\ORM\Assets\Entity\Scientist')
+ ->once()
+ ->andReturnFalse();
+
+ $metadata = m::mock(ClassMetadata::class);
+ $metadata->shouldReceive('getName')
+ ->once()
+ ->andReturn('LaravelDoctrineTest\ORM\Assets\Entity\Theory');
+
+ $metadataFactory->shouldReceive('getAllMetadata')
+ ->once()
+ ->andReturn([$metadata]);
+
+ $entityManager->shouldReceive('getMetadataFactory')
+ ->andReturn($metadataFactory);
+
+ $this->assertNull($this->registry->getManagerForClass('LaravelDoctrineTest\ORM\Assets\Entity\Scientist'));
+ }
+
public function testGetManagerForClassThrowsExceptionWhenNotFound(): void
{
$this->expectException(RuntimeException::class);
@@ -515,7 +546,7 @@ public function testGetManagerForClassThrowsExceptionWhenNotFound(): void
$entityManager->shouldReceive('getMetadataFactory')
->andReturn($metadataFactory);
- $this->assertEquals($entityManager, $this->registry->getManagerForClass('LaravelDoctrineTest\ORM\Assets\Entity\Scientist'));
+ $this->assertEquals($entityManager, $this->registry->getManagerForClass('LaravelDoctrineTest\ORM\Assets\Entity\Scientist', true));
}
public function testGetManagerForClassInvalidClass(): void
diff --git a/tests/Feature/Testing/Concerns/InteractsWithEntitiesTest.php b/tests/Feature/Testing/Concerns/InteractsWithEntitiesTest.php
deleted file mode 100644
index bb9371dd..00000000
--- a/tests/Feature/Testing/Concerns/InteractsWithEntitiesTest.php
+++ /dev/null
@@ -1,100 +0,0 @@
-em = Mockery::mock(EntityManagerInterface::class);
-
- $this->app = Mockery::mock(Container::class);
- $this->app
- ->allows('make')
- ->with('em')
- ->andReturn($this->em);
-
- parent::setUp();
- }
-
- public function testEntitiesMatchWithMatch(): void
- {
- $repository = Mockery::mock(EntityRepository::class);
- $repository->expects('findBy')
- ->with(['someField' => 'someValue'])
- ->once()
- ->andReturn(['entity']);
-
- $this->em->expects('getRepository')
- ->with('SomeClass')
- ->once()
- ->andReturn($repository);
-
- $this->entitiesMatch('SomeClass', ['someField' => 'someValue']);
- }
-
- public function testEntitiesMatchWithoutMatch(): void
- {
- $repository = Mockery::mock(EntityRepository::class);
- $repository->expects('findBy')
- ->with(['someField' => 'someValue'])
- ->once()
- ->andReturn([]);
-
- $this->em->expects('getRepository')
- ->with('SomeClass')
- ->once()
- ->andReturn($repository);
-
- $this->expectException(ExpectationFailedException::class);
- $this->entitiesMatch('SomeClass', ['someField' => 'someValue']);
- }
-
- public function testNoEntitiesMatchWithMatch(): void
- {
- $repository = Mockery::mock(EntityRepository::class);
- $repository->expects('findBy')
- ->with(['someField' => 'someValue'])
- ->once()
- ->andReturn(['entity']);
-
- $this->em->expects('getRepository')
- ->with('SomeClass')
- ->once()
- ->andReturn($repository);
-
- $this->expectException(ExpectationFailedException::class);
- $this->noEntitiesMatch('SomeClass', ['someField' => 'someValue']);
- }
-
- public function testNoEntitiesMatchWithoutMatch(): void
- {
- $repository = Mockery::mock(EntityRepository::class);
- $repository->expects('findBy')
- ->with(['someField' => 'someValue'])
- ->once()
- ->andReturn([]);
-
- $this->em->expects('getRepository')
- ->with('SomeClass')
- ->once()
- ->andReturn($repository);
-
- $this->noEntitiesMatch('SomeClass', ['someField' => 'someValue']);
- }
-}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 315aaa79..c20aabf4 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -4,29 +4,20 @@
namespace LaravelDoctrineTest\ORM;
-use Illuminate\Foundation\Application;
-use PHPUnit\Framework\TestCase as PHPUnitTestCase;
+use Orchestra\Testbench\Concerns\WithWorkbench;
+use Orchestra\Testbench\TestCase as OrchestraTestCase;
-class TestCase extends PHPUnitTestCase
+class TestCase extends OrchestraTestCase
{
- private Application $application;
+ use WithWorkbench;
protected function setUp(): void
{
- $this->application = new Application();
-
parent::setUp();
}
protected function tearDown(): void
{
- unset($this->application);
-
parent::tearDown();
}
-
- public function getApplication(): Application
- {
- return $this->application;
- }
}
diff --git a/workbench/app/Models/.gitkeep b/workbench/app/Models/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/workbench/app/Models/User.php b/workbench/app/Models/User.php
new file mode 100644
index 00000000..1405251f
--- /dev/null
+++ b/workbench/app/Models/User.php
@@ -0,0 +1,45 @@
+
+ */
+ protected $fillable = [
+ 'name',
+ 'email',
+ 'password',
+ ];
+
+ /**
+ * The attributes that should be hidden for serialization.
+ *
+ * @var array
+ */
+ protected $hidden = [
+ 'password',
+ 'remember_token',
+ ];
+
+ /**
+ * The attributes that should be cast.
+ *
+ * @var array
+ */
+ protected $casts = [
+ 'email_verified_at' => 'datetime',
+ 'password' => 'hashed',
+ ];
+}
diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php
new file mode 100644
index 00000000..e8cec9c2
--- /dev/null
+++ b/workbench/app/Providers/WorkbenchServiceProvider.php
@@ -0,0 +1,24 @@
+withRouting(
+ web: __DIR__.'/../routes/web.php',
+ commands: __DIR__.'/../routes/console.php',
+ )
+ ->withMiddleware(function (Middleware $middleware) {
+ //
+ })
+ ->withExceptions(function (Exceptions $exceptions) {
+ //
+ })->create();
diff --git a/workbench/bootstrap/providers.php b/workbench/bootstrap/providers.php
new file mode 100644
index 00000000..3ac44ad1
--- /dev/null
+++ b/workbench/bootstrap/providers.php
@@ -0,0 +1,5 @@
+
+ */
+class UserFactory extends Factory
+{
+ /**
+ * The current password being used by the factory.
+ */
+ protected static ?string $password;
+
+ /**
+ * The name of the factory's corresponding model.
+ *
+ * @var class-string
+ */
+ protected $model = User::class;
+
+ /**
+ * Define the model's default state.
+ *
+ * @return array
+ */
+ public function definition(): array
+ {
+ return [
+ 'name' => fake()->name(),
+ 'email' => fake()->unique()->safeEmail(),
+ 'email_verified_at' => now(),
+ 'password' => static::$password ??= Hash::make('password'),
+ 'remember_token' => Str::random(10),
+ ];
+ }
+
+ /**
+ * Indicate that the model's email address should be unverified.
+ */
+ public function unverified(): static
+ {
+ return $this->state(fn (array $attributes) => [
+ 'email_verified_at' => null,
+ ]);
+ }
+}
diff --git a/workbench/database/migrations/.gitkeep b/workbench/database/migrations/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/workbench/database/seeders/DatabaseSeeder.php b/workbench/database/seeders/DatabaseSeeder.php
new file mode 100644
index 00000000..ce9bd159
--- /dev/null
+++ b/workbench/database/seeders/DatabaseSeeder.php
@@ -0,0 +1,23 @@
+create();
+
+ UserFactory::new()->create([
+ 'name' => 'Test User',
+ 'email' => 'test@example.com',
+ ]);
+ }
+}
diff --git a/workbench/resources/views/.gitkeep b/workbench/resources/views/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/workbench/routes/console.php b/workbench/routes/console.php
new file mode 100644
index 00000000..eff2ed24
--- /dev/null
+++ b/workbench/routes/console.php
@@ -0,0 +1,8 @@
+comment(Inspiring::quote());
+})->purpose('Display an inspiring quote')->hourly();
diff --git a/workbench/routes/web.php b/workbench/routes/web.php
new file mode 100644
index 00000000..86a06c53
--- /dev/null
+++ b/workbench/routes/web.php
@@ -0,0 +1,7 @@
+