diff --git a/.circleci/config.yml b/.circleci/config.yml index bc3f7642..78cf3f90 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -75,9 +75,9 @@ references: - APP_KEY: deela5kinohw0haekoothahSh8eexach jobs: - "php-8.1-apache": + "php-8.2-apache": docker: - - image: php:8.1-apache + - image: php:8.2-apache environment: *environment - *mysql - *mariadb @@ -86,9 +86,9 @@ jobs: - *mariadb2 - *pgsql2 <<: *steps - "php-8.1-nginx": + "php-8.2-nginx": docker: - - image: php:8.1-fpm + - image: php:8.2-fpm environment: *environment - *mysql - *mariadb @@ -97,10 +97,10 @@ jobs: - *mariadb2 - *pgsql2 <<: *steps - "php-8.2-apache": + "php-8.3-apache": docker: - - image: php:8.2-apache - environment: *environment + - image: php:8.3-apache + environment: *environment - *mysql - *mariadb - *pgsql @@ -108,10 +108,32 @@ jobs: - *mariadb2 - *pgsql2 <<: *steps - "php-8.2-nginx": + "php-8.3-nginx": docker: - - image: php:8.2-fpm - environment: *environment + - image: php:8.3-fpm + environment: *environment + - *mysql + - *mariadb + - *pgsql + - *mysql2 + - *mariadb2 + - *pgsql2 + <<: *steps + "php-8.4-apache": + docker: + - image: php:8.4-apache + environment: *environment + - *mysql + - *mariadb + - *pgsql + - *mysql2 + - *mariadb2 + - *pgsql2 + <<: *steps + "php-8.4-nginx": + docker: + - image: php:8.4-fpm + environment: *environment - *mysql - *mariadb - *pgsql @@ -121,7 +143,7 @@ jobs: <<: *steps coverage: docker: - - image: php:8.1-fpm + - image: php:8.4-fpm environment: *environment - *mariadb - *mariadb2 @@ -150,20 +172,26 @@ workflows: version: 2 test: jobs: - - "php-8.1-apache" - - "php-8.1-nginx": + - "php-8.2-apache" + - "php-8.2-nginx": requires: - - "php-8.1-apache" - - "php-8.2-apache": + - "php-8.2-apache" + - "php-8.3-apache": requires: - - "php-8.1-nginx" - - "php-8.2-nginx": + - "php-8.3-nginx" + - "php-8.3-nginx": requires: - - "php-8.2-apache" + - "php-8.3-apache" + - "php-8.4-apache": + requires: + - "php-8.4-nginx" + - "php-8.4-nginx": + requires: + - "php-8.4-apache" - coverage: filters: branches: only: /^([0-9]+)\.x$/ requires: - - "php-8.1-apache" - - "php-8.1-nginx" + - "php-8.4-apache" + - "php-8.4-nginx" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..2e16f17d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# Dockerfile +FROM php:8.4-cli + +# Installiere System-Abhängigkeiten +RUN apt-get update && apt-get install -y \ + git \ + zip \ + unzip \ + libzip-dev \ + libicu-dev \ + libonig-dev \ + && docker-php-ext-install zip pdo_mysql + +# Composer installieren +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +# Arbeitsverzeichnis setzen +WORKDIR /var/www/html + +# Projektdateien kopieren (optional; wenn du direkt via Volume mountest, ist dies evtl. nicht notwendig) +# COPY . /var/www/html + +# Pakete installieren, Cache reduzieren +RUN composer global require "laravel/installer" + +# Pfad zu Composer bin hinzufügen +ENV PATH="/root/.composer/vendor/bin:${PATH}" + +# Standardkommando: Zeigt nur PHP-Version an (placeholder) +CMD ["php", "-v"] diff --git a/composer.json b/composer.json index cd5ab273..0c32291e 100644 --- a/composer.json +++ b/composer.json @@ -23,16 +23,16 @@ "docs": "https://tenancy.dev" }, "require": { - "php": "^8.0", - "doctrine/dbal": "~2.5|~3.0", + "php": "^8.2", "ramsey/uuid": "^4.0", - "laravel/framework": "^9.0|^10.0" + "laravel/framework": "^12.0", + "doctrine/dbal": "^3.9" }, "require-dev": { "fakerphp/faker": "^1.12", - "laravel/laravel": "^9.0|^10.0", + "laravel/laravel": "^12.0", "mockery/mockery": "^1.0", - "phpunit/phpunit": "^9.0", + "phpunit/phpunit": "^11.0", "symfony/dom-crawler": "~3.1" }, "autoload": { diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..db5f1e53 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,25 @@ +services: + mysql: + image: 'mysql:8.0' + ports: + - '3306:3306' + environment: + MYSQL_DATABASE: 'tenancy' + MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' + command: --default-authentication-plugin=mysql_native_password + app: + build: + context: . + dockerfile: Dockerfile + container_name: hyn_app + working_dir: /var/www/html + volumes: + - .:/var/www/html + depends_on: + - mysql + + command: > + bash -c "composer install + && vendor/bin/phpunit" +volumes: + db_data: diff --git a/phpunit.xml b/phpunit.xml index 880b975a..c351c894 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -16,13 +16,17 @@ + + + + - + diff --git a/readme.md b/readme.md index 5413577a..309fd90a 100644 --- a/readme.md +++ b/readme.md @@ -35,8 +35,8 @@ Database separation methods: ## Requirements, recommended environment -- Laravel 9.0+. -- PHP 8.0+ +- Laravel 12.0+. +- PHP 8.2+ - Apache or Nginx. - MySQL, MariaDB, or PostgreSQL. diff --git a/src/Abstracts/AbstractTenantDirectoryListener.php b/src/Abstracts/AbstractTenantDirectoryListener.php index 5ba2c764..1de9e732 100644 --- a/src/Abstracts/AbstractTenantDirectoryListener.php +++ b/src/Abstracts/AbstractTenantDirectoryListener.php @@ -126,7 +126,7 @@ protected function path() return $this->directory->path($this->path); } - protected function directory(Website $website = null): Directory + protected function directory(?Website $website = null): Directory { /** @var Directory $directory */ $directory = app(Directory::class); diff --git a/src/Abstracts/DatabaseEvent.php b/src/Abstracts/DatabaseEvent.php index a88f3750..d08a9efa 100644 --- a/src/Abstracts/DatabaseEvent.php +++ b/src/Abstracts/DatabaseEvent.php @@ -28,7 +28,7 @@ abstract class DatabaseEvent extends AbstractEvent */ public $config; - public function __construct(array &$config, Website $website = null) + public function __construct(array &$config, ?Website $website = null) { $this->config = &$config; $this->website = &$website; diff --git a/src/Abstracts/HostnameEvent.php b/src/Abstracts/HostnameEvent.php index 65e22734..1409fd62 100644 --- a/src/Abstracts/HostnameEvent.php +++ b/src/Abstracts/HostnameEvent.php @@ -23,7 +23,7 @@ abstract class HostnameEvent extends AbstractEvent */ public $hostname; - public function __construct(Hostname &$hostname = null) + public function __construct(?Hostname &$hostname = null) { $this->hostname = &$hostname; } diff --git a/src/Abstracts/WebsiteEvent.php b/src/Abstracts/WebsiteEvent.php index ca32a06c..e2be7d5c 100644 --- a/src/Abstracts/WebsiteEvent.php +++ b/src/Abstracts/WebsiteEvent.php @@ -29,7 +29,7 @@ abstract class WebsiteEvent extends AbstractEvent */ public $hostname; - public function __construct(Website &$website, Hostname $hostname = null) + public function __construct(?Website &$website, ?Hostname $hostname = null) { $this->website = &$website; $this->hostname = $hostname; diff --git a/src/Database/Connection.php b/src/Database/Connection.php index 04526c7e..e6f2b285 100644 --- a/src/Database/Connection.php +++ b/src/Database/Connection.php @@ -124,7 +124,7 @@ public function get(): \Illuminate\Database\Connection * @param string|null $connection * @return bool */ - public function exists(string $connection = null): bool + public function exists(?string $connection = null): bool { $connection = $connection ?? $this->tenantName(); @@ -178,7 +178,7 @@ public function set($to, $connection = null): bool return true; } - public function configuration(string $connection = null): array + public function configuration(?string $connection = null): array { $connection = $connection ?? $this->tenantName(); @@ -244,7 +244,7 @@ public function purge($connection = null) * @param string|null $path * @return bool */ - public function migrate($for, string $path = null): bool + public function migrate($for, ?string $path = null): bool { $website = $this->convertWebsiteOrHostnameToWebsite($for); @@ -273,7 +273,7 @@ public function migrate($for, string $path = null): bool * @param string $class * @return bool */ - public function seed($for, string $class = null): bool + public function seed($for, ?string $class = null): bool { $website = $this->convertWebsiteOrHostnameToWebsite($for); diff --git a/src/Environment.php b/src/Environment.php index 6de81300..50149e00 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -83,6 +83,8 @@ public function identifyHostname() return $hostname; }); + // needed, does not work without it in laravel 12, but does in 10 (don't know why) + $this->app->make(CurrentHostname::class); } /** @@ -91,7 +93,7 @@ public function identifyHostname() * @param Hostname|null $hostname * @return Hostname|null */ - public function hostname(Hostname $hostname = null): ?Hostname + public function hostname(?Hostname $hostname = null): ?Hostname { if ($hostname !== null) { $this->app->instance(CurrentHostname::class, $hostname); @@ -117,7 +119,7 @@ public function website(): ?Website * @param Website|null $website * @return Tenant|null */ - public function tenant(Website $website = null): ?Website + public function tenant(?Website $website = null): ?Website { if ($website !== null) { $this->app->instance(Tenant::class, $website); diff --git a/src/Events/Database/ConnectionSet.php b/src/Events/Database/ConnectionSet.php index 4192d433..371744ee 100644 --- a/src/Events/Database/ConnectionSet.php +++ b/src/Events/Database/ConnectionSet.php @@ -38,7 +38,7 @@ class ConnectionSet extends AbstractEvent */ public $purged; - public function __construct(Website $website = null, string $connection, bool $purged = true) + public function __construct(?Website $website = null, string $connection, bool $purged = true) { $this->website = $website; $this->connection = $connection; diff --git a/src/Events/Hostnames/Updated.php b/src/Events/Hostnames/Updated.php index 88300987..7117b389 100644 --- a/src/Events/Hostnames/Updated.php +++ b/src/Events/Hostnames/Updated.php @@ -24,7 +24,7 @@ class Updated extends HostnameEvent */ public $dirty; - public function __construct(Hostname $hostname = null, array $dirty = []) + public function __construct(?Hostname $hostname = null, array $dirty = []) { parent::__construct($hostname); diff --git a/src/Events/Websites/NoneFound.php b/src/Events/Websites/NoneFound.php index 7ebf0201..ba028f8e 100644 --- a/src/Events/Websites/NoneFound.php +++ b/src/Events/Websites/NoneFound.php @@ -28,7 +28,7 @@ class NoneFound extends AbstractEvent * NoneFound constructor. * @param Request $request */ - public function __construct(Request $request = null) + public function __construct(?Request $request = null) { $this->request = $request; } diff --git a/src/Providers/Tenants/QueueProvider.php b/src/Providers/Tenants/QueueProvider.php index 1b6f01b8..300d4a30 100644 --- a/src/Providers/Tenants/QueueProvider.php +++ b/src/Providers/Tenants/QueueProvider.php @@ -29,7 +29,7 @@ public function boot() { $this->app->booted(function () { $this->app->extend('queue', function (QueueManager $queue) { - $queue->createPayloadUsing(function (string $connection, string $queue = null, array $payload = []) { + $queue->createPayloadUsing(function (string $connection, ?string $queue = null, array $payload = []) { /** @var Environment $environment */ $environment = resolve(Environment::class); diff --git a/src/Traits/DispatchesJobs.php b/src/Traits/DispatchesJobs.php index 2673eeb9..a3575c41 100644 --- a/src/Traits/DispatchesJobs.php +++ b/src/Traits/DispatchesJobs.php @@ -25,6 +25,6 @@ trait DispatchesJobs */ public function dispatch($command, $handler = null) { - return app(Dispatcher::class)->dispatchNow($command, $handler); + return app(Dispatcher::class)->dispatchSync($command, $handler); } } diff --git a/src/Website/Directory.php b/src/Website/Directory.php index 15edf9e5..f139d5ab 100644 --- a/src/Website/Directory.php +++ b/src/Website/Directory.php @@ -74,11 +74,11 @@ public function setWebsite(Website $website): Directory } /** - * @param string $path + * @param $path * @param bool $local * @return string */ - public function path(string $path = null, $local = false): string + public function path($path = null, $local = false): string { $prefix = "{$this->getWebsite()->uuid}/"; @@ -134,6 +134,34 @@ public function put($path, $contents, $visibility = null) ); } + public function putFile($path, $file = null, $options = []) + { + return $this->filesystem->putFile( + $this->path($path), + $file, + $options + ); + } + + /** + * Store the uploaded file on the disk with a given name. + * + * @param \Illuminate\Http\File|\Illuminate\Http\UploadedFile|string $path + * @param \Illuminate\Http\File|\Illuminate\Http\UploadedFile|string|array|null $file + * @param string|array|null $name + * @param mixed $options + * @return string|false + */ + public function putFileAs($path, $file, $name = null, $options = []) + { + return $this->filesystem->putFileAs( + $this->path($path), + $file, + $name, + $options + ); + } + /** * Get the visibility for the given path. * diff --git a/tests/traits/InteractsWithMigrations.php b/tests/traits/InteractsWithMigrations.php index 5617a309..6c30eb45 100644 --- a/tests/traits/InteractsWithMigrations.php +++ b/tests/traits/InteractsWithMigrations.php @@ -49,11 +49,11 @@ protected function migrateSystem() /** * @param string $command - * @param callable|null $callback - * @param callable|null $hook + * @param ?callable|null $callback + * @param ?callable|null $hook * @param array $commandOptions */ - protected function migrateAndTest(string $command, callable $callback = null, callable $hook = null, array $commandOptions = []) + protected function migrateAndTest(string $command, ?callable $callback = null, ?callable $hook = null, array $commandOptions = []) { $code = $this->artisan("tenancy:$command", array_merge([ '--realpath' => true, @@ -76,10 +76,10 @@ protected function migrateAndTest(string $command, callable $callback = null, ca } /** - * @param callable|null $callback - * @param callable|null $hook + * @param ?callable|null $callback + * @param ?callable|null $hook */ - protected function seedAndTest(callable $callback = null, callable $hook = null) + protected function seedAndTest(?callable $callback = null, ?callable $hook = null) { $code = $this->artisan("tenancy:db:seed", [ '--class' => SampleSeeder::class, diff --git a/tests/traits/InteractsWithRoutes.php b/tests/traits/InteractsWithRoutes.php index ed677d4e..23a00d22 100644 --- a/tests/traits/InteractsWithRoutes.php +++ b/tests/traits/InteractsWithRoutes.php @@ -22,7 +22,7 @@ trait InteractsWithRoutes { - protected function create_and_test_route(string $path, string $uri = null) + protected function create_and_test_route(string $path, ?string $uri = null) { /** @var Directory $directory */ $directory = $this->app->make(Directory::class); diff --git a/tests/unit-tests/Commands/RunCommandTest.php b/tests/unit-tests/Commands/RunCommandTest.php index 068a3e47..e3f32f9f 100644 --- a/tests/unit-tests/Commands/RunCommandTest.php +++ b/tests/unit-tests/Commands/RunCommandTest.php @@ -14,7 +14,7 @@ namespace Hyn\Tenancy\Tests\Commands; -use App\Console\Kernel; +use Illuminate\Foundation\Console\Kernel; use Hyn\Tenancy\Tests\Test; use Illuminate\Contracts\Foundation\Application; diff --git a/tests/unit-tests/Commands/SeedCommandTest.php b/tests/unit-tests/Commands/SeedCommandTest.php index 866ea742..4789eee7 100644 --- a/tests/unit-tests/Commands/SeedCommandTest.php +++ b/tests/unit-tests/Commands/SeedCommandTest.php @@ -18,6 +18,7 @@ use Hyn\Tenancy\Models\Website; use Illuminate\Contracts\Console\Kernel; use Hyn\Tenancy\Tests\Seeds\SampleSeeder; +use Illuminate\Support\Facades\Schema; class SeedCommandTest extends DatabaseCommandTest { @@ -49,8 +50,8 @@ public function runs_seed_on_one_tenant() $this->connection->set($this->website); - $this->assertFalse($this->connection->get()->getDoctrineSchemaManager()->tablesExist('users')); - $this->assertTrue($this->connection->get()->getDoctrineSchemaManager()->tablesExist('samples')); + $this->assertFalse(Schema::connection($this->connection->tenantName())->hasTable('users')); + $this->assertTrue(Schema::connection($this->connection->tenantName())->hasTable('samples')); $this->assertGreaterThan( 0, @@ -87,8 +88,8 @@ public function runs_configured_seed() $this->connection->set($this->website); - $this->assertFalse($this->connection->get()->getDoctrineSchemaManager()->tablesExist('users')); - $this->assertTrue($this->connection->get()->getDoctrineSchemaManager()->tablesExist('samples')); + $this->assertFalse(Schema::connection($this->connection->tenantName())->hasTable('users')); + $this->assertTrue(Schema::connection($this->connection->tenantName())->hasTable('samples')); } /** @@ -98,14 +99,14 @@ public function runs_seed_on_tenants() { $this->connection->set($this->website); - $this->assertFalse($this->connection->get()->getDoctrineSchemaManager()->tablesExist('samples')); + $this->assertFalse(Schema::connection($this->connection->tenantName())->hasTable('samples')); $this->migrateAndTest('migrate'); $this->seedAndTest(function (Website $website) { $this->connection->set($website); - $this->assertTrue($this->connection->get()->getDoctrineSchemaManager()->tablesExist('samples')); + $this->assertTrue(Schema::connection($this->connection->tenantName())->hasTable('samples')); $this->assertEquals( 2, diff --git a/tests/unit-tests/Middleware/HostnameActionsTest.php b/tests/unit-tests/Middleware/HostnameActionsTest.php index e26064e9..a3168e88 100644 --- a/tests/unit-tests/Middleware/HostnameActionsTest.php +++ b/tests/unit-tests/Middleware/HostnameActionsTest.php @@ -89,7 +89,7 @@ public function auto_identification_false() } } - protected function middleware(Hostname $set = null) + protected function middleware(?Hostname $set = null) { app(Environment::class)->hostname($set); diff --git a/tests/unit-tests/Providers/RouteProviderTest.php b/tests/unit-tests/Providers/RouteProviderTest.php index b29614b1..bce4a2e1 100644 --- a/tests/unit-tests/Providers/RouteProviderTest.php +++ b/tests/unit-tests/Providers/RouteProviderTest.php @@ -50,7 +50,7 @@ public function overrides_global_route() { $this->overrideGlobalRoute(); - $this->assertEquals(2, $this->app['router']->getRoutes()->count()); + $this->assertEquals(3, $this->app['router']->getRoutes()->count()); } /** diff --git a/tests/unit-tests/Repositories/HostnameRepositoryTest.php b/tests/unit-tests/Repositories/HostnameRepositoryTest.php index 403ea7d9..33d683d9 100644 --- a/tests/unit-tests/Repositories/HostnameRepositoryTest.php +++ b/tests/unit-tests/Repositories/HostnameRepositoryTest.php @@ -159,7 +159,7 @@ public function hostname_regex_validation_no_matches(string $hostname) } } - protected function matchHostnames(): array + public static function matchHostnames(): array { return [ ["xn-fsqu00a.xn-0zwm56d"], @@ -185,7 +185,7 @@ protected function matchHostnames(): array ]; } - protected function noMatchHostnames(): array + public static function noMatchHostnames(): array { return [ ["-0-0O.COM"],