From 54d920f5fa974af7c20553f60b7c8f913e599a79 Mon Sep 17 00:00:00 2001
From: mszulik <69617961+mszulik@users.noreply.github.com>
Date: Wed, 11 Dec 2024 15:42:32 +0100
Subject: [PATCH 1/2] fix an issue where the tablespaces configuration option
was not applied (#98)
---
docker-compose.yml | 3 +++
src/Classes/MySqlSchemaStateProxy.php | 9 +--------
src/Classes/PostgresSchemaStateProxy.php | 9 +--------
src/ProtectorServiceProvider.php | 5 +++++
src/Traits/HasConfiguration.php | 5 +++++
5 files changed, 15 insertions(+), 16 deletions(-)
diff --git a/docker-compose.yml b/docker-compose.yml
index 3f46631..d7c7fcf 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -19,6 +19,7 @@ services:
- '.:/var/www/html'
networks:
- internal
+ - shared
depends_on:
- mysql_testing
mysql_testing:
@@ -39,3 +40,5 @@ services:
networks:
internal:
internal: true
+ shared:
+ external: true
diff --git a/src/Classes/MySqlSchemaStateProxy.php b/src/Classes/MySqlSchemaStateProxy.php
index 8573106..27bce0e 100644
--- a/src/Classes/MySqlSchemaStateProxy.php
+++ b/src/Classes/MySqlSchemaStateProxy.php
@@ -32,14 +32,6 @@ public function dump(Connection $connection, $path)
}
}
- /**
- * @inheritDoc
- */
- public function load($path)
- {
- $this->schemaState->load(...func_get_args());
- }
-
/**
* Get the dump command for MySQL as a string.
*/
@@ -53,6 +45,7 @@ protected function getCommandString(): string
'--skip-comments' => !$this->protector->shouldDumpComments(),
'--skip-set-charset' => !$this->protector->shouldDumpCharsets(),
'--no-data' => !$this->protector->shouldDumpData(),
+ '--no-tablespaces' => !$this->protector->shouldUseTablespaces(),
];
$parameters = [
diff --git a/src/Classes/PostgresSchemaStateProxy.php b/src/Classes/PostgresSchemaStateProxy.php
index c5feb10..2bc7d8e 100644
--- a/src/Classes/PostgresSchemaStateProxy.php
+++ b/src/Classes/PostgresSchemaStateProxy.php
@@ -25,14 +25,6 @@ public function dump(Connection $connection, $path)
]));
}
- /**
- * {@inheritDoc}
- */
- public function load($path)
- {
- $this->schemaState->load(...func_get_args());
- }
-
protected function getBaseDumpArguments(): array
{
$conditionalArguments = [
@@ -40,6 +32,7 @@ protected function getBaseDumpArguments(): array
'--clean' => $this->protector->shouldCreateDb() && $this->protector->shouldDropDb(),
'--verbose' => $this->protector->shouldDumpComments(),
'--schema-only' => !$this->protector->shouldDumpData(),
+ '--no-tablespaces' => !$this->protector->shouldUseTablespaces(),
];
return [
diff --git a/src/ProtectorServiceProvider.php b/src/ProtectorServiceProvider.php
index cfa6896..db971cd 100644
--- a/src/ProtectorServiceProvider.php
+++ b/src/ProtectorServiceProvider.php
@@ -3,6 +3,7 @@
namespace Cybex\Protector;
use Cybex\Protector\Classes\MySqlSchemaStateProxy;
+use Cybex\Protector\Classes\PostgresSchemaStateProxy;
use Cybex\Protector\Commands\CreateKeys;
use Cybex\Protector\Commands\CreateToken;
use Cybex\Protector\Commands\ExportDump;
@@ -54,6 +55,10 @@ public function register(): void
$this->app->bind(MySqlSchemaStateProxy::class, function ($app, array $params) {
return new MySqlSchemaStateProxy(...$params);
});
+
+ $this->app->bind(PostgresSchemaStateProxy::class, function ($app, array $params) {
+ return new PostgresSchemaStateProxy(...$params);
+ });
}
protected function registerRoutes()
diff --git a/src/Traits/HasConfiguration.php b/src/Traits/HasConfiguration.php
index 5a37c2e..b2b7c3a 100644
--- a/src/Traits/HasConfiguration.php
+++ b/src/Traits/HasConfiguration.php
@@ -277,4 +277,9 @@ public function shouldRemoveAutoIncrementingState(): bool
{
return $this->removeAutoIncrementingState;
}
+
+ public function shouldUseTablespaces(): bool
+ {
+ return $this->tablespaces;
+ }
}
From 032af52f4aba3ebe2e0570952bc254e18036798f Mon Sep 17 00:00:00 2001
From: mszulik <69617961+mszulik@users.noreply.github.com>
Date: Wed, 11 Dec 2024 15:58:35 +0100
Subject: [PATCH 2/2] add a test for the protector configuration options (#99)
---
.env | 4 +-
docker-compose.yml => compose.yml | 0
phpunit.xml.dist | 4 +-
sail | 603 ++++++++++++++++++
src/Classes/AbstractMySqlSchemaStateProxy.php | 5 +
.../AbstractPostgresSchemaStateProxy.php | 5 +
src/Classes/MySqlSchemaStateProxy.php | 23 +-
src/Classes/PostgresSchemaStateProxy.php | 15 +-
src/Traits/HasConfiguration.php | 72 +++
tests/feature/ExportDumpTest.php | 218 ++++++-
10 files changed, 922 insertions(+), 27 deletions(-)
rename docker-compose.yml => compose.yml (100%)
create mode 100755 sail
diff --git a/.env b/.env
index b872e8d..05ae2b3 100644
--- a/.env
+++ b/.env
@@ -2,5 +2,5 @@
APP_SERVICE=app
DB_HOST=laravel-protector-mysql_testing-1
DB_DATABASE=protector_test
-DB_USERNAME=forge
-DB_PASSWORD=forge
+DB_USERNAME=protector
+DB_PASSWORD=protector
diff --git a/docker-compose.yml b/compose.yml
similarity index 100%
rename from docker-compose.yml
rename to compose.yml
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index dc3e135..d7e3dde 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -24,8 +24,8 @@
-
-
+
+
diff --git a/sail b/sail
new file mode 100755
index 0000000..d9685f0
--- /dev/null
+++ b/sail
@@ -0,0 +1,603 @@
+#!/usr/bin/env bash
+
+UNAMEOUT="$(uname -s)"
+
+# Verify operating system is supported...
+case "${UNAMEOUT}" in
+ Linux*) MACHINE=linux;;
+ Darwin*) MACHINE=mac;;
+ *) MACHINE="UNKNOWN"
+esac
+
+if [ "$MACHINE" == "UNKNOWN" ]; then
+ echo "Unsupported operating system [$(uname -s)]. Laravel Sail supports macOS, Linux, and Windows (WSL2)." >&2
+
+ exit 1
+fi
+
+# Determine if stdout is a terminal...
+if test -t 1; then
+ # Determine if colors are supported...
+ ncolors=$(tput colors)
+
+ if test -n "$ncolors" && test "$ncolors" -ge 8; then
+ BOLD="$(tput bold)"
+ YELLOW="$(tput setaf 3)"
+ GREEN="$(tput setaf 2)"
+ NC="$(tput sgr0)"
+ fi
+fi
+
+# Function that prints the available commands...
+function display_help {
+ echo "Laravel Sail"
+ echo
+ echo "${YELLOW}Usage:${NC}" >&2
+ echo " sail COMMAND [options] [arguments]"
+ echo
+ echo "Unknown commands are passed to the docker-compose binary."
+ echo
+ echo "${YELLOW}docker-compose Commands:${NC}"
+ echo " ${GREEN}sail up${NC} Start the application"
+ echo " ${GREEN}sail up -d${NC} Start the application in the background"
+ echo " ${GREEN}sail stop${NC} Stop the application"
+ echo " ${GREEN}sail restart${NC} Restart the application"
+ echo " ${GREEN}sail ps${NC} Display the status of all containers"
+ echo
+ echo "${YELLOW}Artisan Commands:${NC}"
+ echo " ${GREEN}sail artisan ...${NC} Run an Artisan command"
+ echo " ${GREEN}sail artisan queue:work${NC}"
+ echo
+ echo "${YELLOW}PHP Commands:${NC}"
+ echo " ${GREEN}sail php ...${NC} Run a snippet of PHP code"
+ echo " ${GREEN}sail php -v${NC}"
+ echo
+ echo "${YELLOW}Composer Commands:${NC}"
+ echo " ${GREEN}sail composer ...${NC} Run a Composer command"
+ echo " ${GREEN}sail composer require laravel/sanctum${NC}"
+ echo
+ echo "${YELLOW}Node Commands:${NC}"
+ echo " ${GREEN}sail node ...${NC} Run a Node command"
+ echo " ${GREEN}sail node --version${NC}"
+ echo
+ echo "${YELLOW}NPM Commands:${NC}"
+ echo " ${GREEN}sail npm ...${NC} Run a npm command"
+ echo " ${GREEN}sail npx${NC} Run a npx command"
+ echo " ${GREEN}sail npm run prod${NC}"
+ echo
+ echo "${YELLOW}PNPM Commands:${NC}"
+ echo " ${GREEN}sail pnpm ...${NC} Run a pnpm command"
+ echo " ${GREEN}sail pnpx${NC} Run a pnpx command"
+ echo " ${GREEN}sail pnpm run prod${NC}"
+ echo
+ echo "${YELLOW}Yarn Commands:${NC}"
+ echo " ${GREEN}sail yarn ...${NC} Run a Yarn command"
+ echo " ${GREEN}sail yarn run prod${NC}"
+ echo
+ echo "${YELLOW}Bun Commands:${NC}"
+ echo " ${GREEN}sail bun ...${NC} Run a bun command"
+ echo " ${GREEN}sail bunx${NC} Run a bunx command"
+ echo " ${GREEN}sail bun run prod${NC}"
+ echo
+ echo "${YELLOW}Database Commands:${NC}"
+ echo " ${GREEN}sail mysql${NC} Start a MySQL CLI session within the 'mysql' container"
+ echo " ${GREEN}sail mariadb${NC} Start a MySQL CLI session within the 'mariadb' container"
+ echo " ${GREEN}sail psql${NC} Start a PostgreSQL CLI session within the 'pgsql' container"
+ echo " ${GREEN}sail mongodb${NC} Start a Mongo Shell session within the 'mongodb' container"
+ echo " ${GREEN}sail redis${NC} Start a Redis CLI session within the 'redis' container"
+ echo
+ echo "${YELLOW}Debugging:${NC}"
+ echo " ${GREEN}sail debug ...${NC} Run an Artisan command in debug mode"
+ echo " ${GREEN}sail debug queue:work${NC}"
+ echo
+ echo "${YELLOW}Running Tests:${NC}"
+ echo " ${GREEN}sail test${NC} Run the PHPUnit tests via the Artisan test command"
+ echo " ${GREEN}sail phpunit ...${NC} Run PHPUnit"
+ echo " ${GREEN}sail pest ...${NC} Run Pest"
+ echo " ${GREEN}sail pint ...${NC} Run Pint"
+ echo " ${GREEN}sail dusk${NC} Run the Dusk tests (Requires the laravel/dusk package)"
+ echo " ${GREEN}sail dusk:fails${NC} Re-run previously failed Dusk tests (Requires the laravel/dusk package)"
+ echo
+ echo "${YELLOW}Container CLI:${NC}"
+ echo " ${GREEN}sail shell${NC} Start a shell session within the application container"
+ echo " ${GREEN}sail bash${NC} Alias for 'sail shell'"
+ echo " ${GREEN}sail root-shell${NC} Start a root shell session within the application container"
+ echo " ${GREEN}sail root-bash${NC} Alias for 'sail root-shell'"
+ echo " ${GREEN}sail tinker${NC} Start a new Laravel Tinker session"
+ echo
+ echo "${YELLOW}Sharing:${NC}"
+ echo " ${GREEN}sail share${NC} Share the application publicly via a temporary URL"
+ echo " ${GREEN}sail open${NC} Open the site in your browser"
+ echo
+ echo "${YELLOW}Binaries:${NC}"
+ echo " ${GREEN}sail bin ...${NC} Run Composer binary scripts from the vendor/bin directory"
+ echo
+ echo "${YELLOW}Customization:${NC}"
+ echo " ${GREEN}sail artisan sail:publish${NC} Publish the Sail configuration files"
+ echo " ${GREEN}sail build --no-cache${NC} Rebuild all of the Sail containers"
+
+ exit 1
+}
+
+# Proxy the "help" command...
+if [ $# -gt 0 ]; then
+ if [ "$1" == "help" ] || [ "$1" == "-h" ] || [ "$1" == "-help" ] || [ "$1" == "--help" ]; then
+ display_help
+ fi
+else
+ display_help
+fi
+
+# Source the ".env" file so Laravel's environment variables are available...
+# shellcheck source=/dev/null
+if [ -n "$APP_ENV" ] && [ -f ./.env."$APP_ENV" ]; then
+ source ./.env."$APP_ENV";
+elif [ -f ./.env ]; then
+ source ./.env;
+fi
+
+# Define environment variables...
+export APP_PORT=${APP_PORT:-80}
+export APP_SERVICE=${APP_SERVICE:-"laravel.test"}
+export DB_PORT=${DB_PORT:-3306}
+export WWWUSER=${WWWUSER:-$UID}
+export WWWGROUP=${WWWGROUP:-$(id -g)}
+
+export SAIL_FILES=${SAIL_FILES:-""}
+export SAIL_SHARE_DASHBOARD=${SAIL_SHARE_DASHBOARD:-4040}
+export SAIL_SHARE_SERVER_HOST=${SAIL_SHARE_SERVER_HOST:-"laravel-sail.site"}
+export SAIL_SHARE_SERVER_PORT=${SAIL_SHARE_SERVER_PORT:-8080}
+export SAIL_SHARE_SUBDOMAIN=${SAIL_SHARE_SUBDOMAIN:-""}
+export SAIL_SHARE_DOMAIN=${SAIL_SHARE_DOMAIN:-"$SAIL_SHARE_SERVER_HOST"}
+export SAIL_SHARE_SERVER=${SAIL_SHARE_SERVER:-""}
+
+# Function that outputs Sail is not running...
+function sail_is_not_running {
+ echo "${BOLD}Sail is not running.${NC}" >&2
+ echo "" >&2
+ echo "${BOLD}You may Sail using the following commands:${NC} './vendor/bin/sail up' or './vendor/bin/sail up -d'" >&2
+
+ exit 1
+}
+
+# Define Docker Compose command prefix...
+if docker compose &> /dev/null; then
+ DOCKER_COMPOSE=(docker compose)
+else
+ DOCKER_COMPOSE=(docker-compose)
+fi
+
+if [ -n "$SAIL_FILES" ]; then
+ # Convert SAIL_FILES to an array...
+ IFS=':' read -ra SAIL_FILES <<< "$SAIL_FILES"
+
+ for FILE in "${SAIL_FILES[@]}"; do
+ if [ -f "$FILE" ]; then
+ DOCKER_COMPOSE+=(-f "$FILE")
+ else
+ echo "${BOLD}Unable to find Docker Compose file: '${FILE}'${NC}" >&2
+
+ exit 1
+ fi
+ done
+fi
+
+EXEC="yes"
+
+if [ -z "$SAIL_SKIP_CHECKS" ]; then
+ # Ensure that Docker is running...
+ if ! docker info > /dev/null 2>&1; then
+ echo "${BOLD}Docker is not running.${NC}" >&2
+
+ exit 1
+ fi
+
+ # Determine if Sail is currently up...
+ if "${DOCKER_COMPOSE[@]}" ps "$APP_SERVICE" 2>&1 | grep 'Exit\|exited'; then
+ echo "${BOLD}Shutting down old Sail processes...${NC}" >&2
+
+ "${DOCKER_COMPOSE[@]}" down > /dev/null 2>&1
+
+ EXEC="no"
+ elif [ -z "$("${DOCKER_COMPOSE[@]}" ps -q)" ]; then
+ EXEC="no"
+ fi
+fi
+
+ARGS=()
+
+# Proxy PHP commands to the "php" binary on the application container...
+if [ "$1" == "php" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" "php")
+ else
+ sail_is_not_running
+ fi
+
+# Proxy vendor binary commands on the application container...
+elif [ "$1" == "bin" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ CMD=$1
+ shift 1
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" ./vendor/bin/"$CMD")
+ else
+ sail_is_not_running
+ fi
+
+# Proxy docker-compose commands to the docker-compose binary on the application container...
+elif [ "$1" == "docker-compose" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" "${DOCKER_COMPOSE[@]}")
+ else
+ sail_is_not_running
+ fi
+
+# Proxy Composer commands to the "composer" binary on the application container...
+elif [ "$1" == "composer" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" "composer")
+ else
+ sail_is_not_running
+ fi
+
+# Proxy Artisan commands to the "artisan" binary on the application container...
+elif [ "$1" == "artisan" ] || [ "$1" == "art" ] || [ "$1" == "a" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" php artisan)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy the "debug" command to the "php artisan" binary on the application container with xdebug enabled...
+elif [ "$1" == "debug" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail -e XDEBUG_TRIGGER=1)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" php artisan)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy the "test" command to the "php artisan test" Artisan command...
+elif [ "$1" == "test" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" php artisan test)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy the "phpunit" command to "php vendor/bin/phpunit"...
+elif [ "$1" == "phpunit" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" php vendor/bin/phpunit)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy the "pest" command to "php vendor/bin/pest"...
+elif [ "$1" == "pest" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" php vendor/bin/pest)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy the "pint" command to "php vendor/bin/pint"...
+elif [ "$1" == "pint" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" php vendor/bin/pint)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy the "dusk" command to the "php artisan dusk" Artisan command...
+elif [ "$1" == "dusk" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=(-e "APP_URL=http://${APP_SERVICE}")
+ ARGS+=(-e "DUSK_DRIVER_URL=http://selenium:4444/wd/hub")
+ ARGS+=("$APP_SERVICE" php artisan dusk)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy the "dusk:fails" command to the "php artisan dusk:fails" Artisan command...
+elif [ "$1" == "dusk:fails" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=(-e "APP_URL=http://${APP_SERVICE}")
+ ARGS+=(-e "DUSK_DRIVER_URL=http://selenium:4444/wd/hub")
+ ARGS+=("$APP_SERVICE" php artisan dusk:fails)
+ else
+ sail_is_not_running
+ fi
+
+# Initiate a Laravel Tinker session within the application container...
+elif [ "$1" == "tinker" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" php artisan tinker)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy Node commands to the "node" binary on the application container...
+elif [ "$1" == "node" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" node)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy NPM commands to the "npm" binary on the application container...
+elif [ "$1" == "npm" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" npm)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy NPX commands to the "npx" binary on the application container...
+elif [ "$1" == "npx" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" npx)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy PNPM commands to the "pnpm" binary on the application container...
+elif [ "$1" == "pnpm" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" pnpm)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy PNPX commands to the "pnpx" binary on the application container...
+elif [ "$1" == "pnpx" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" pnpx)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy Yarn commands to the "yarn" binary on the application container...
+elif [ "$1" == "yarn" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" yarn)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy Bun commands to the "bun" binary on the application container...
+elif [ "$1" == "bun" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" bun)
+ else
+ sail_is_not_running
+ fi
+
+# Proxy Bun X commands to the "bunx" binary on the application container...
+elif [ "$1" == "bunx" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" bunx)
+ else
+ sail_is_not_running
+ fi
+
+# Initiate a MySQL CLI terminal session within the "mysql" container...
+elif [ "$1" == "mysql" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=(mysql bash -c)
+ ARGS+=("MYSQL_PWD=\${MYSQL_PASSWORD} mysql -u \${MYSQL_USER} \${MYSQL_DATABASE}")
+ else
+ sail_is_not_running
+ fi
+
+# Initiate a MySQL CLI terminal session within the "mariadb" container...
+elif [ "$1" == "mariadb" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=(mariadb bash -c)
+ ARGS+=("MYSQL_PWD=\${MYSQL_PASSWORD} mariadb -u \${MYSQL_USER} \${MYSQL_DATABASE}")
+ else
+ sail_is_not_running
+ fi
+
+# Initiate a PostgreSQL CLI terminal session within the "pgsql" container...
+elif [ "$1" == "psql" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=(pgsql bash -c)
+ ARGS+=("PGPASSWORD=\${PGPASSWORD} psql -U \${POSTGRES_USER} \${POSTGRES_DB}")
+ else
+ sail_is_not_running
+ fi
+
+# Initiate a Bash shell within the application container...
+elif [ "$1" == "shell" ] || [ "$1" == "bash" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u sail)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" bash)
+ else
+ sail_is_not_running
+ fi
+
+# Initiate a root user Bash shell within the application container...
+elif [ "$1" == "root-shell" ] || [ "$1" == "root-bash" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec -u root)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=("$APP_SERVICE" bash)
+ else
+ sail_is_not_running
+ fi
+
+# Initiate a MongoDB Shell within the "mongodb" container...
+elif [ "$1" == "mongodb" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=(mongodb mongosh --port "${FORWARD_MONGODB_PORT:-27017}" --username "$MONGODB_USERNAME" --password "$MONGODB_PASSWORD" --authenticationDatabase admin)
+ else
+ sail_is_not_running
+ fi
+
+# Initiate a Redis CLI terminal session within the "redis" container...
+elif [ "$1" == "redis" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ ARGS+=(exec)
+ [ ! -t 0 ] && ARGS+=(-T)
+ ARGS+=(redis redis-cli)
+ else
+ sail_is_not_running
+ fi
+
+# Share the site...
+elif [ "$1" == "share" ]; then
+ shift 1
+
+ if [ "$EXEC" == "yes" ]; then
+ docker run --init --rm --add-host=host.docker.internal:host-gateway -p "$SAIL_SHARE_DASHBOARD":4040 -t beyondcodegmbh/expose-server:latest share http://host.docker.internal:"$APP_PORT" \
+ --server-host="$SAIL_SHARE_SERVER_HOST" \
+ --server-port="$SAIL_SHARE_SERVER_PORT" \
+ --auth="$SAIL_SHARE_TOKEN" \
+ --server="$SAIL_SHARE_SERVER" \
+ --subdomain="$SAIL_SHARE_SUBDOMAIN" \
+ --domain="$SAIL_SHARE_DOMAIN" \
+ "$@"
+
+ exit
+ else
+ sail_is_not_running
+ fi
+
+# Open the site...
+elif [ "$1" == "open" ]; then
+ shift 1
+
+ if command -v open &>/dev/null; then
+ OPEN="open"
+ elif command -v xdg-open &>/dev/null; then
+ OPEN="xdg-open"
+ else
+ echo "Neither open nor xdg-open is available. Exiting."
+ exit 1
+ fi
+
+ if [ "$EXEC" == "yes" ]; then
+
+ if [[ -n "$APP_PORT" && "$APP_PORT" != "80" ]]; then
+ FULL_URL="${APP_URL}:${APP_PORT}"
+ else
+ FULL_URL="$APP_URL"
+ fi
+
+ $OPEN "$FULL_URL"
+
+ exit
+ else
+ sail_is_not_running
+ fi
+fi
+
+# Run Docker Compose with the defined arguments...
+"${DOCKER_COMPOSE[@]}" "${ARGS[@]}" "$@"
diff --git a/src/Classes/AbstractMySqlSchemaStateProxy.php b/src/Classes/AbstractMySqlSchemaStateProxy.php
index a74f53c..064c62b 100644
--- a/src/Classes/AbstractMySqlSchemaStateProxy.php
+++ b/src/Classes/AbstractMySqlSchemaStateProxy.php
@@ -58,4 +58,9 @@ protected function appendMigrationData(string $path): void
{
$this->schemaState->appendMigrationData(...func_get_args());
}
+
+ public function getConditionalParameters(): array
+ {
+ return [];
+ }
}
diff --git a/src/Classes/AbstractPostgresSchemaStateProxy.php b/src/Classes/AbstractPostgresSchemaStateProxy.php
index fdae46e..0bd4d1b 100644
--- a/src/Classes/AbstractPostgresSchemaStateProxy.php
+++ b/src/Classes/AbstractPostgresSchemaStateProxy.php
@@ -59,4 +59,9 @@ protected function getBaseDumpArguments(): array
'--dbname="${:LARAVEL_LOAD_DATABASE}"',
];
}
+
+ public function getConditionalParameters(): array
+ {
+ return [];
+ }
}
diff --git a/src/Classes/MySqlSchemaStateProxy.php b/src/Classes/MySqlSchemaStateProxy.php
index 27bce0e..a336aa9 100644
--- a/src/Classes/MySqlSchemaStateProxy.php
+++ b/src/Classes/MySqlSchemaStateProxy.php
@@ -39,15 +39,6 @@ protected function getCommandString(): string
{
$command = 'mysqldump '.$this->schemaState->connectionString().' ';
- $conditionalParameters = [
- '--set-gtid-purged=OFF' => !$this->schemaState->connection->isMaria(),
- '--no-create-db' => !$this->protector->shouldCreateDb(),
- '--skip-comments' => !$this->protector->shouldDumpComments(),
- '--skip-set-charset' => !$this->protector->shouldDumpCharsets(),
- '--no-data' => !$this->protector->shouldDumpData(),
- '--no-tablespaces' => !$this->protector->shouldUseTablespaces(),
- ];
-
$parameters = [
'--add-locks',
'--routines',
@@ -55,10 +46,22 @@ protected function getCommandString(): string
'--column-statistics=0',
'--result-file="${:LARAVEL_LOAD_PATH}"',
'--max-allowed-packet='.$this->protector->getMaxPacketLength(),
- ...array_keys(array_filter($conditionalParameters)),
+ ...array_keys(array_filter($this->getConditionalParameters())),
'"${:LARAVEL_LOAD_DATABASE}"',
];
return $command.implode(' ', $parameters);
}
+
+ public function getConditionalParameters(): array
+ {
+ return [
+ '--set-gtid-purged=OFF' => !$this->schemaState->connection->isMaria(),
+ '--no-create-db' => !$this->protector->shouldCreateDb(),
+ '--skip-comments' => !$this->protector->shouldDumpComments(),
+ '--skip-set-charset' => !$this->protector->shouldDumpCharsets(),
+ '--no-data' => !$this->protector->shouldDumpData(),
+ '--no-tablespaces' => !$this->protector->shouldUseTablespaces(),
+ ];
+ }
}
diff --git a/src/Classes/PostgresSchemaStateProxy.php b/src/Classes/PostgresSchemaStateProxy.php
index 2bc7d8e..24e746f 100644
--- a/src/Classes/PostgresSchemaStateProxy.php
+++ b/src/Classes/PostgresSchemaStateProxy.php
@@ -27,17 +27,20 @@ public function dump(Connection $connection, $path)
protected function getBaseDumpArguments(): array
{
- $conditionalArguments = [
+ return [
+ ...parent::getBaseDumpArguments(),
+ ...array_keys(array_filter($this->getConditionalParameters())),
+ ];
+ }
+
+ public function getConditionalParameters(): array
+ {
+ return [
'--create' => $this->protector->shouldCreateDb(),
'--clean' => $this->protector->shouldCreateDb() && $this->protector->shouldDropDb(),
'--verbose' => $this->protector->shouldDumpComments(),
'--schema-only' => !$this->protector->shouldDumpData(),
'--no-tablespaces' => !$this->protector->shouldUseTablespaces(),
];
-
- return [
- ...parent::getBaseDumpArguments(),
- ...array_keys(array_filter($conditionalArguments)),
- ];
}
}
diff --git a/src/Traits/HasConfiguration.php b/src/Traits/HasConfiguration.php
index b2b7c3a..1403f5f 100644
--- a/src/Traits/HasConfiguration.php
+++ b/src/Traits/HasConfiguration.php
@@ -23,6 +23,7 @@ trait HasConfiguration
/**
* Defines whether existing databases should be dropped before importing a dump.
+ * Only works if used together with the $createDb option.
* (PostgreSQL only, controls the --clean flag)
*/
protected bool $dropDb = true;
@@ -97,6 +98,13 @@ public function withAuthTokenKeyName(string $authTokenKeyName): static
return $this;
}
+ public function withAutoIncrementingState(): static
+ {
+ $this->removeAutoIncrementingState = false;
+
+ return $this;
+ }
+
public function withoutAutoIncrementingState(): static
{
$this->removeAutoIncrementingState = true;
@@ -104,6 +112,13 @@ public function withoutAutoIncrementingState(): static
return $this;
}
+ public function withCharsets(): static
+ {
+ $this->dumpCharsets = true;
+
+ return $this;
+ }
+
public function withoutCharsets(): static
{
$this->dumpCharsets = false;
@@ -111,6 +126,13 @@ public function withoutCharsets(): static
return $this;
}
+ public function withComments(): static
+ {
+ $this->dumpComments = true;
+
+ return $this;
+ }
+
public function withoutComments(): static
{
$this->dumpComments = false;
@@ -118,6 +140,13 @@ public function withoutComments(): static
return $this;
}
+ public function withData(): static
+ {
+ $this->dumpData = true;
+
+ return $this;
+ }
+
public function withoutData(): static
{
$this->dumpData = false;
@@ -139,6 +168,13 @@ public function withConnectionName(?string $connectionName = null): static
return $this;
}
+ public function withCreateDb(): static
+ {
+ $this->createDb = true;
+
+ return $this;
+ }
+
public function withoutCreateDb(): static
{
$this->createDb = false;
@@ -146,6 +182,37 @@ public function withoutCreateDb(): static
return $this;
}
+ /**
+ * Defines that existing databases should be dropped before importing a dump.
+ * Only works if used together with the $createDb option.
+ * (PostgreSQL only, controls the --clean flag)
+ */
+ public function withDropDb(): static
+ {
+ $this->dropDb = true;
+
+ return $this;
+ }
+
+ /**
+ * Defines that existing databases should not be dropped before importing a dump.
+ * Only works if used together with the $createDb option
+ * (PostgreSQL only, controls the --clean flag)
+ */
+ public function withoutDropDb(): static
+ {
+ $this->dropDb = false;
+
+ return $this;
+ }
+
+ public function withTablespaces(): static
+ {
+ $this->tablespaces = true;
+
+ return $this;
+ }
+
public function withoutTablespaces(): static
{
$this->tablespaces = false;
@@ -240,6 +307,11 @@ protected function getPrivateKey(): string
return env($this->privateKeyName, '');
}
+ public function getConnectionName(): string
+ {
+ return $this->connectionName;
+ }
+
/**
* Retrieves the server url of the dump endpoint.
*/
diff --git a/tests/feature/ExportDumpTest.php b/tests/feature/ExportDumpTest.php
index 53c9608..0ab6d4d 100644
--- a/tests/feature/ExportDumpTest.php
+++ b/tests/feature/ExportDumpTest.php
@@ -7,6 +7,7 @@
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
+use Illuminate\Support\Facades\DB;
use PDOException;
use Symfony\Component\HttpFoundation\StreamedResponse;
@@ -18,6 +19,40 @@ class ExportDumpTest extends TestCase
protected string $filePath;
protected string $emptyDumpPath;
+ const POSTGRES_CREATE = '--create';
+ const POSTGRES_CLEAN = '--clean';
+ const POSTGRES_VERBOSE = '--verbose';
+ const POSTGRES_SCHEMA_ONLY = '--schema-only';
+ const NO_TABLESPACES = '--no-tablespaces';
+ const MYSQL_SKIP_GTID_INFO = '--set-gtid-purged=OFF';
+ const MYSQL_NO_CREATE_DB = '--no-create-db';
+ const MYSQL_SKIP_COMMENTS = '--skip-comments';
+ const MYSQL_SKIP_SET_CHARSET = '--skip-set-charset';
+ const MYSQL_NO_DATA = '--no-data';
+ const PROTECTOR_WITH_CREATE_DB = 'withCreateDb';
+ const PROTECTOR_WITH_DROP_DB = 'withDropDb';
+ const PROTECTOR_WITH_COMMENTS = 'withComments';
+ const PROTECTOR_WITH_CHARSETS = 'withCharsets';
+ const PROTECTOR_WITH_DATA = 'withData';
+ const PROTECTOR_WITH_TABLESPACES = 'withTablespaces';
+ const PROTECTOR_CONFIG_BASELINE = [
+ 'pgsql' => [
+ self::POSTGRES_CREATE => false,
+ self::POSTGRES_CLEAN => false,
+ self::POSTGRES_VERBOSE => false,
+ self::POSTGRES_SCHEMA_ONLY => true,
+ self::NO_TABLESPACES => true,
+ ],
+ 'mysql' => [
+ self::MYSQL_SKIP_GTID_INFO => true,
+ self::MYSQL_NO_CREATE_DB => true,
+ self::MYSQL_SKIP_COMMENTS => true,
+ self::MYSQL_SKIP_SET_CHARSET => true,
+ self::MYSQL_NO_DATA => true,
+ self::NO_TABLESPACES => true,
+ ],
+ ];
+
protected function setUp(): void
{
parent::setUp();
@@ -25,7 +60,7 @@ protected function setUp(): void
$this->disk = $this->getFakeDumpDisk();
$this->baseDirectory = Config::get('protector.baseDirectory');
- $this->filePath = sprintf('%s/dump.sql', $this->baseDirectory);
+ $this->filePath = sprintf('%s/dump.sql', $this->baseDirectory);
$this->emptyDumpPath = 'testDumps/dump.sql';
}
@@ -36,7 +71,7 @@ public function createDestinationFilePath()
{
$this->disk->deleteDirectory(Config::get('protector.baseDirectory'));
- $filePath = $this->protector->createDestinationFilePath(__FUNCTION__);
+ $filePath = $this->protector->createDestinationFilePath(__FUNCTION__);
$destinationFilePath = $this->disk->path($filePath);
$this->runProtectedMethod('createDirectory', [$filePath, $this->disk]);
@@ -50,7 +85,7 @@ public function createDestinationFilePathWithSubFolder()
{
$this->disk->deleteDirectory(Config::get('protector.baseDirectory'));
- $filePath = $this->protector->createDestinationFilePath(__FUNCTION__, __FUNCTION__);
+ $filePath = $this->protector->createDestinationFilePath(__FUNCTION__, __FUNCTION__);
$destinationFilePath = $this->disk->path($filePath);
$this->runProtectedMethod('createDirectory', [$filePath, $this->disk]);
@@ -99,10 +134,10 @@ public function failGeneratingDumpWhenTryingToConnectToDatabase()
{
// Provide an database connection to a non-existing database.
Config::set('database.connections.invalid', [
- 'driver' => 'mysql',
- 'url' => env('DATABASE_URL'),
- 'host' => env('DB_HOST', '127.0.0.1'),
- 'port' => env('DB_PORT', '3306'),
+ 'driver' => 'mysql',
+ 'url' => env('DATABASE_URL'),
+ 'host' => env('DB_HOST', '127.0.0.1'),
+ 'port' => env('DB_PORT', '3306'),
'database' => 'invalid_database_name',
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
@@ -128,4 +163,173 @@ public function createsStreamedFileDownloadResponse()
$this->assertInstanceOf(StreamedResponse::class, $response);
$this->assertEquals(200, $response->getStatusCode());
}
+
+ /**
+ * @test
+ * @dataProvider provideForHasCorrectConfiguration
+ */
+ public function hasCorrectConfiguration(array $protectorOptions, array $expected): void
+ {
+ $this->configureProtector($protectorOptions);
+
+ $connection = DB::connection($this->protector->getConnectionName());
+ $schemaState = $connection->getSchemaState();
+ $schemaStateProxy = $this->runProtectedMethod('getProxyForSchemaState', [$schemaState]);
+
+ $conditionalParameters = $schemaStateProxy->getConditionalParameters();
+
+ $this->assertEquals($expected[$connection->getDriverName()], $conditionalParameters);
+ }
+
+ public static function provideForHasCorrectConfiguration(): array
+ {
+ return [
+ 'all options on' => [
+ 'protectorOptions' => [
+ self::PROTECTOR_WITH_CREATE_DB,
+ self::PROTECTOR_WITH_DROP_DB,
+ self::PROTECTOR_WITH_COMMENTS,
+ self::PROTECTOR_WITH_CHARSETS,
+ self::PROTECTOR_WITH_DATA,
+ self::PROTECTOR_WITH_TABLESPACES,
+ ],
+ 'expected' => self::getExpected([
+ 'pgsql' => [
+ self::POSTGRES_CREATE => true,
+ self::POSTGRES_CLEAN => true,
+ self::POSTGRES_VERBOSE => true,
+ self::POSTGRES_SCHEMA_ONLY => false,
+ self::NO_TABLESPACES => false,
+ ],
+ 'mysql' => [
+ self::MYSQL_SKIP_GTID_INFO => true,
+ self::MYSQL_NO_CREATE_DB => false,
+ self::MYSQL_SKIP_COMMENTS => false,
+ self::MYSQL_SKIP_SET_CHARSET => false,
+ self::MYSQL_NO_DATA => false,
+ self::NO_TABLESPACES => false,
+ ]
+ ])
+ ],
+ 'all options off' => [
+ 'protectorOptions' => [],
+ 'expected' => self::getExpected()
+ ],
+ 'postgres purge db' => [
+ 'protectorOptions' => [
+ self::PROTECTOR_WITH_CREATE_DB,
+ self::PROTECTOR_WITH_DROP_DB
+ ],
+ 'expected' => self::getExpected([
+ 'pgsql' => [
+ self::POSTGRES_CREATE => true,
+ self::POSTGRES_CLEAN => true,
+ ],
+ 'mysql' => [
+ self::MYSQL_NO_CREATE_DB => false,
+ ],
+ ])
+ ],
+ 'create db' => [
+ 'protectorOptions' => [
+ self::PROTECTOR_WITH_CREATE_DB
+ ],
+ 'expected' => self::getExpected([
+ 'pgsql' => [
+ self::POSTGRES_CREATE => true,
+ ],
+ 'mysql' => [
+ self::MYSQL_NO_CREATE_DB => false,
+ ],
+ ])
+ ],
+ 'drop db' => [
+ 'protectorOptions' => [
+ self::PROTECTOR_WITH_DROP_DB
+ ],
+ 'expected' => self::getExpected()
+ ],
+ 'dump comments' => [
+ 'protectorOptions' => [
+ self::PROTECTOR_WITH_COMMENTS
+ ],
+ 'expected' => self::getExpected([
+ 'pgsql' => [
+ self::POSTGRES_VERBOSE => true,
+ ],
+ 'mysql' => [
+ self::MYSQL_SKIP_COMMENTS => false,
+ ],
+ ])
+ ],
+ 'dump charsets' => [
+ 'protectorOptions' => [
+ self::PROTECTOR_WITH_CHARSETS
+ ],
+ 'expected' => self::getExpected([
+ 'mysql' => [
+ self::MYSQL_SKIP_SET_CHARSET => false,
+ ],
+ ])
+ ],
+ 'dump data' => [
+ 'protectorOptions' => [
+ self::PROTECTOR_WITH_DATA
+ ],
+ 'expected' => self::getExpected([
+ 'pgsql' => [
+ self::POSTGRES_SCHEMA_ONLY => false,
+ ],
+ 'mysql' => [
+ self::MYSQL_NO_DATA => false,
+ ],
+ ])
+ ],
+ 'dump tablespaces' => [
+ 'protectorOptions' => [
+ self::PROTECTOR_WITH_TABLESPACES
+ ],
+ 'expected' => self::getExpected([
+ 'pgsql' => [
+ self::NO_TABLESPACES => false,
+ ],
+ 'mysql' => [
+ self::NO_TABLESPACES => false,
+ ],
+ ])
+ ],
+ ];
+ }
+
+ /**
+ * Merges the baseline array (all protector config options set to 'without') with the provided deviations.
+ *
+ * @param array $deviations
+ * @return array
+ */
+ protected static function getExpected(array $deviations = []): array
+ {
+ return array_replace_recursive(self::PROTECTOR_CONFIG_BASELINE, $deviations);
+ }
+
+ /**
+ * Configures the protector with the provided options.
+ *
+ * @param array $protectorOptions
+ * @return void
+ */
+ protected function configureProtector(array $protectorOptions): void
+ {
+ $this->protector
+ ->withoutCreateDb()
+ ->withoutDropDb()
+ ->withoutComments()
+ ->withoutCharsets()
+ ->withoutData()
+ ->withoutTablespaces();
+
+ foreach ($protectorOptions as $option) {
+ $this->protector->$option();
+ }
+ }
}