From 0efa078034e5f440f76d74d486b241dd13fd250b Mon Sep 17 00:00:00 2001 From: NiallSmyth Date: Sat, 23 Apr 2016 16:30:07 +0100 Subject: [PATCH 01/17] MySqlIntegerColumn didn't retain default value --- src/Repositories/MySql/Schema/Columns/MySqlIntegerColumn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Repositories/MySql/Schema/Columns/MySqlIntegerColumn.php b/src/Repositories/MySql/Schema/Columns/MySqlIntegerColumn.php index 5f9ffcd..716ab31 100644 --- a/src/Repositories/MySql/Schema/Columns/MySqlIntegerColumn.php +++ b/src/Repositories/MySql/Schema/Columns/MySqlIntegerColumn.php @@ -37,6 +37,6 @@ public function getDefinition() protected static function fromGenericColumnType(Column $genericColumn) { - return new MySqlIntegerColumn($genericColumn->columnName); + return new self($genericColumn->columnName, $genericColumn->defaultValue); } } From 503a3b2080503116a93730fdb5de1098f2e180e9 Mon Sep 17 00:00:00 2001 From: NiallSmyth Date: Sun, 1 May 2016 15:22:35 +0100 Subject: [PATCH 02/17] Ensure repo has had chance to set timezone from mysql database before using it --- src/Repositories/MySql/Schema/Columns/MySqlDateTimeColumn.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Repositories/MySql/Schema/Columns/MySqlDateTimeColumn.php b/src/Repositories/MySql/Schema/Columns/MySqlDateTimeColumn.php index df05bed..75da3f7 100644 --- a/src/Repositories/MySql/Schema/Columns/MySqlDateTimeColumn.php +++ b/src/Repositories/MySql/Schema/Columns/MySqlDateTimeColumn.php @@ -19,6 +19,7 @@ namespace Rhubarb\Stem\Repositories\MySql\Schema\Columns; use Rhubarb\Crown\DateTime\RhubarbDateTime; +use Rhubarb\Stem\Repositories\MySql\MySql; use Rhubarb\Stem\Schema\Columns\Column; use Rhubarb\Stem\Schema\Columns\DateTimeColumn; use Rhubarb\Stem\StemSettings; @@ -61,6 +62,9 @@ public function getTransformIntoRepository() $date = clone $data; $settings = new StemSettings(); + if (!$settings->RepositoryTimeZone) { + MySql::getDefaultConnection(); + } if ($settings->RepositoryTimeZone) { // Normalise timezones to default system timezone when stored in DB $date->setTimezone($settings->RepositoryTimeZone); From 94db305abcdfb3904885d8d351325e407d100ca2 Mon Sep 17 00:00:00 2001 From: NiallSmyth Date: Tue, 24 May 2016 17:40:36 +0100 Subject: [PATCH 03/17] Don't throw a LoginFailedException if the duplicate user accounts are inactive --- src/LoginProviders/ModelLoginProvider.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/LoginProviders/ModelLoginProvider.php b/src/LoginProviders/ModelLoginProvider.php index 0675b49..830fc08 100644 --- a/src/LoginProviders/ModelLoginProvider.php +++ b/src/LoginProviders/ModelLoginProvider.php @@ -78,9 +78,16 @@ public function login($username, $password) // There should only be one user matching the username. It would be possible to support // unique *combinations* of username and password but it's a potential security issue and // could trip us up when supporting the project. - if (sizeof($list) > 1) { - Log::debug("Login failed for {$username} - the username wasn't unique", "LOGIN"); - throw new LoginFailedException(); + $existingActiveUsers = 0; + foreach ($list as $user) { + if ($this->isModelActive($user)) { + $existingActiveUsers++; + } + + if ($existingActiveUsers > 1) { + Log::debug("Login failed for {$username} - the username wasn't unique", "LOGIN"); + throw new LoginFailedException(); + } } /** From e5c5161889eb7508373912be048d2e287e8749cc Mon Sep 17 00:00:00 2001 From: NiallSmyth Date: Wed, 25 May 2016 10:40:34 +0100 Subject: [PATCH 04/17] Removing version requirements --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7c8bc6d..cd0b176 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "bin-dir": "bin/" }, "require": { - "rhubarbphp/rhubarb": "1.0.x-dev@dev" + "rhubarbphp/rhubarb": "*" }, "require-dev": { "rhubarbphp/custard" : "^1.0.4", From 346acf8d7fe97cccc09a4d182f9c768af57360c4 Mon Sep 17 00:00:00 2001 From: NiallSmyth Date: Tue, 14 Jun 2016 14:08:42 +0100 Subject: [PATCH 05/17] Setting initial fileVersion to null in case a hashed version begins with "0" (which would be seen as equal to int 0) --- src/Schema/SolutionSchema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Schema/SolutionSchema.php b/src/Schema/SolutionSchema.php index ecabd8a..d770b59 100644 --- a/src/Schema/SolutionSchema.php +++ b/src/Schema/SolutionSchema.php @@ -679,7 +679,7 @@ public function checkModelSchemasIfNecessary() } $versionFile = "cache/schema-versions/" . str_replace("\\", "_", get_class($this)) . ".txt"; - $fileVersion = 0; + $fileVersion = null; $context = new Context(); From 2d1929b6208f0b147363130693dc16eee698a6e5 Mon Sep 17 00:00:00 2001 From: NiallSmyth Date: Fri, 16 Dec 2016 11:47:25 +0000 Subject: [PATCH 06/17] Null is an invalid default for a primary key field --- src/Schema/Columns/UUIDColumn.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Schema/Columns/UUIDColumn.php b/src/Schema/Columns/UUIDColumn.php index b4d0b09..897416e 100644 --- a/src/Schema/Columns/UUIDColumn.php +++ b/src/Schema/Columns/UUIDColumn.php @@ -1,10 +1,4 @@ Date: Wed, 1 Mar 2017 12:10:54 +0000 Subject: [PATCH 07/17] Transform autohydrated data when caching it, so it's formatted right when we try to use it later --- src/Repositories/MySql/MySql.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Repositories/MySql/MySql.php b/src/Repositories/MySql/MySql.php index 277cb97..11891b6 100644 --- a/src/Repositories/MySql/MySql.php +++ b/src/Repositories/MySql/MySql.php @@ -295,6 +295,8 @@ public function getUniqueIdentifiersForDataList(Collection $list, &$unfetchedRow $modelData = array_combine($modelJoinedColumns, $joinedData); + $modelData = $repository->transformDataFromRepository($modelData); + $repository->cachedObjectData[$modelData[$model->UniqueIdentifierColumnName]] = $modelData; } From 289cd9405ca66633813db1935bc7b29945f57fc1 Mon Sep 17 00:00:00 2001 From: NiallSmyth Date: Thu, 2 Mar 2017 08:45:41 +0000 Subject: [PATCH 08/17] Making mysql charset configurable --- src/Repositories/MySql/MySql.php | 2 +- src/StemSettings.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Repositories/MySql/MySql.php b/src/Repositories/MySql/MySql.php index 11891b6..f01dfd1 100644 --- a/src/Repositories/MySql/MySql.php +++ b/src/Repositories/MySql/MySql.php @@ -672,7 +672,7 @@ public static function getConnection(StemSettings $settings) if (!isset(PdoRepository::$connections[$connectionHash])) { try { $pdo = new \PDO( - "mysql:host=" . $settings->Host . ";port=" . $settings->Port . ";dbname=" . $settings->Database . ";charset=utf8", + "mysql:host=" . $settings->Host . ";port=" . $settings->Port . ";dbname=" . $settings->Database . ";charset=" . $settings->Charset, $settings->Username, $settings->Password, [\PDO::ERRMODE_EXCEPTION => true] diff --git a/src/StemSettings.php b/src/StemSettings.php index 2d0a730..cd00257 100644 --- a/src/StemSettings.php +++ b/src/StemSettings.php @@ -26,6 +26,7 @@ * @property string $Username * @property string $Password * @property string $Database + * @property string $Charset * @property \DateTimeZone $RepositoryTimeZone * @property \DateTimeZone $ProjectTimeZone */ @@ -36,6 +37,7 @@ protected function initialiseDefaultValues() parent::initialiseDefaultValues(); $this->Port = 3306; + $this->Charset = 'utf8'; $this->ProjectTimeZone = new \DateTimeZone(date_default_timezone_get()); } } From 6d43d85837be358a09e49d190949131b51c0049b Mon Sep 17 00:00:00 2001 From: NiallSmyth Date: Wed, 5 Apr 2017 09:03:02 +0100 Subject: [PATCH 09/17] Ensure accounts with dupe emails where the first one is disabled can still log in --- src/LoginProviders/ModelLoginProvider.php | 27 +++++++++-------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/LoginProviders/ModelLoginProvider.php b/src/LoginProviders/ModelLoginProvider.php index 830fc08..1392447 100644 --- a/src/LoginProviders/ModelLoginProvider.php +++ b/src/LoginProviders/ModelLoginProvider.php @@ -81,6 +81,7 @@ public function login($username, $password) $existingActiveUsers = 0; foreach ($list as $user) { if ($this->isModelActive($user)) { + $activeUser = $user; $existingActiveUsers++; } @@ -90,29 +91,23 @@ public function login($username, $password) } } - /** - * @var Model $user - */ - $user = $list[0]; + if (!isset($activeUser)) { + Log::debug("Login failed for {$username} - the user is disabled.", "LOGIN"); + throw new LoginDisabledException(); + } - $this->checkUserIsPermitted($user); + $this->checkUserIsPermitted($activeUser); // Test the password matches. - $userPasswordHash = $user[$this->passwordColumnName]; + $userPasswordHash = $activeUser[$this->passwordColumnName]; if ($hashProvider->compareHash($password, $userPasswordHash)) { - // Matching login - but is it enabled? - if ($this->isModelActive($user)) { - $this->LoggedIn = true; - $this->LoggedInUserIdentifier = $user->getUniqueIdentifier(); + $this->loggedIn = true; + $this->loggedInUserIdentifier = $activeUser->getUniqueIdentifier(); - $this->storeSession(); + $this->storeSession(); - return true; - } else { - Log::debug("Login failed for {$username} - the user is disabled.", "LOGIN"); - throw new LoginDisabledException(); - } + return true; } Log::debug("Login failed for {$username} - the password hash $userPasswordHash didn't match the stored hash.", "LOGIN"); From ef133cfadd7ea68a1af56e66a0cfcd2948d5b320 Mon Sep 17 00:00:00 2001 From: NiallSmyth Date: Wed, 5 Apr 2017 11:22:37 +0100 Subject: [PATCH 10/17] Uppercase L for old rhubarb... --- src/LoginProviders/ModelLoginProvider.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LoginProviders/ModelLoginProvider.php b/src/LoginProviders/ModelLoginProvider.php index 1392447..ce56ed7 100644 --- a/src/LoginProviders/ModelLoginProvider.php +++ b/src/LoginProviders/ModelLoginProvider.php @@ -102,8 +102,8 @@ public function login($username, $password) $userPasswordHash = $activeUser[$this->passwordColumnName]; if ($hashProvider->compareHash($password, $userPasswordHash)) { - $this->loggedIn = true; - $this->loggedInUserIdentifier = $activeUser->getUniqueIdentifier(); + $this->LoggedIn = true; + $this->LoggedInUserIdentifier = $activeUser->getUniqueIdentifier(); $this->storeSession(); From 4030470e58da7bc7162ed9844614d351a7db8a19 Mon Sep 17 00:00:00 2001 From: NiallSmyth Date: Wed, 10 May 2017 11:19:58 +0100 Subject: [PATCH 11/17] Setting charset in the connection string can confuse PDO. Setting it with a query afterwards seems to be fine. --- src/Repositories/MySql/MySql.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Repositories/MySql/MySql.php b/src/Repositories/MySql/MySql.php index f01dfd1..b4706f8 100644 --- a/src/Repositories/MySql/MySql.php +++ b/src/Repositories/MySql/MySql.php @@ -672,12 +672,18 @@ public static function getConnection(StemSettings $settings) if (!isset(PdoRepository::$connections[$connectionHash])) { try { $pdo = new \PDO( - "mysql:host=" . $settings->Host . ";port=" . $settings->Port . ";dbname=" . $settings->Database . ";charset=" . $settings->Charset, + "mysql:host=" . $settings->Host . ";port=" . $settings->Port . ";dbname=" . $settings->Database . ";charset=utf8", $settings->Username, $settings->Password, [\PDO::ERRMODE_EXCEPTION => true] ); + if ($settings->Charset != 'utf8') { + // Change charset if it's not the default + $statement = $pdo->prepare("SET NAMES :charset"); + $statement->execute(['charset' => $settings->Charset]); + } + $timeZone = $pdo->query("SELECT @@system_time_zone"); if ($timeZone->rowCount()) { $settings->RepositoryTimeZone = new \DateTimeZone($timeZone->fetchColumn()); From 04eecbddf9789d53d33b27094d4ab81e74b2b7db Mon Sep 17 00:00:00 2001 From: NiallSmyth Date: Thu, 24 Aug 2017 12:03:58 +0100 Subject: [PATCH 12/17] MoneyColumn extends DecimalColumn, so its type formatter has to be registered after or the Decimal one will always replace the Money one. Date formatter was registered for the wrong class (Date control instead of a DateColumn), but putting in a working formatter now may have unintended side effects (if Column decorators are registered for date fields, they'll be expecting a DateTime value, but if this type formatter is present they'll suddenly be getting a string value, then they'll error when they try to treat is an object). --- src/Decorators/CommonDataDecorator.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Decorators/CommonDataDecorator.php b/src/Decorators/CommonDataDecorator.php index 3eba83e..72affa1 100644 --- a/src/Decorators/CommonDataDecorator.php +++ b/src/Decorators/CommonDataDecorator.php @@ -20,7 +20,6 @@ require_once __DIR__ . '/DataDecorator.php'; -use Rhubarb\Leaf\Presenters\Controls\DateTime\Date; use Rhubarb\Stem\Decorators\Formatters\DecimalFormatter; use Rhubarb\Stem\Models\Model; use Rhubarb\Stem\Schema\Columns\BooleanColumn; @@ -38,14 +37,10 @@ protected function registerTypeDefinitions() return $booleanValue ? "Yes" : "No"; }); - $this->addTypeFormatter(MoneyColumn::class, function (Model $model, $value) { - return number_format($value, 2); - }); - $this->addTypeFormatter(DecimalColumn::class, new DecimalFormatter()); - $this->addTypeFormatter(Date::class, function (Model $model, \DateTime $value) { - return $value->format("d-M-Y"); + $this->addTypeFormatter(MoneyColumn::class, function (Model $model, $value) { + return number_format($value, 2); }); } } From 231cfa86333b6d0dfd4c55e7248b79d746add299 Mon Sep 17 00:00:00 2001 From: NiallSmyth Date: Wed, 30 Aug 2017 12:31:27 +0100 Subject: [PATCH 13/17] Improved getter & setter detection to check parameters match a property getter and setter's expected definition --- .../CommandHelpers/GetterOrSetterMethod.php | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Custard/CommandHelpers/GetterOrSetterMethod.php b/src/Custard/CommandHelpers/GetterOrSetterMethod.php index e016fbb..d18f33d 100644 --- a/src/Custard/CommandHelpers/GetterOrSetterMethod.php +++ b/src/Custard/CommandHelpers/GetterOrSetterMethod.php @@ -25,9 +25,40 @@ public static function fromReflectionMethod(\ReflectionMethod $method, $existing $methodName = $method->getName(); if (stripos($methodName, 'get') === 0) { + $parameters = $method->getParameters(); + $wrapper->readable = true; + foreach ($parameters as $parameter) { + if (!$parameter->allowsNull()) { + // If a "get" method has any non-nullable parameters, it's not a property getter + $wrapper->readable = false; + break; + } + } + + if (!$wrapper->readable) { + return false; + } } elseif (stripos($methodName, 'set') === 0) { - $wrapper->writable = true; + $parameters = $method->getParameters(); + $paramCount = count($parameters); + + if ($paramCount > 0) { + // A "set" method must take a parameter to be a property setter + $wrapper->writable = true; + + for ($i = 1; $i < $paramCount; $i++) { + if (!$parameter->allowsNull()) { + // If a "set" method has more than 1 non-nullable parameter, it's not a property setter + $wrapper->writable = false; + break; + } + } + } + + if (!$wrapper->writable) { + return false; + } } else { // Neither a getter nor a setter return false; From 02afeca7be82a775943bc37799d5f142c8e80c96 Mon Sep 17 00:00:00 2001 From: NiallSmyth Date: Tue, 12 Sep 2017 14:45:57 +0100 Subject: [PATCH 14/17] Uppercase Array is seen as a class --- src/Collections/Collection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Collections/Collection.php b/src/Collections/Collection.php index 3071942..5e2dbe9 100644 --- a/src/Collections/Collection.php +++ b/src/Collections/Collection.php @@ -398,7 +398,7 @@ public function filter(Filter $filter) * Where repository specific optimisation is available this will be leveraged to run the batch * update at the data source rather than iterating over the items. * - * @param Array $propertyPairs An associative array of key value pairs to update + * @param array $propertyPairs An associative array of key value pairs to update * @param bool $fallBackToIteration If the repository can't perform the action directly, perform the update by iterating over all the models in the collection. You should only pass true if you know that the collection doesn't meet the criteria for an optimised update and the iteration of items won't cause problems * iterating over all the models in the collection. You should only pass true * if you know that the collection doesn't meet the criteria for an optimised From 9be913681190c05c423fe16aaf4a0547b1ec1106 Mon Sep 17 00:00:00 2001 From: Michael Miscampbell Date: Mon, 21 Jan 2019 12:24:40 +0000 Subject: [PATCH 15/17] Adding seeder scenario improvements --- .../DescribedDemoDataSeederInterface.php | 27 ++++ src/Custard/Scenario.php | 55 ++++++++ src/Custard/ScenarioDataSeeder.php | 47 +++++++ src/Custard/ScenarioDescription.php | 32 +++++ src/Custard/SeedDemoDataCommand.php | 131 ++++++++++++++---- 5 files changed, 266 insertions(+), 26 deletions(-) create mode 100644 src/Custard/DescribedDemoDataSeederInterface.php create mode 100644 src/Custard/Scenario.php create mode 100644 src/Custard/ScenarioDataSeeder.php create mode 100644 src/Custard/ScenarioDescription.php diff --git a/src/Custard/DescribedDemoDataSeederInterface.php b/src/Custard/DescribedDemoDataSeederInterface.php new file mode 100644 index 0000000..c80a551 --- /dev/null +++ b/src/Custard/DescribedDemoDataSeederInterface.php @@ -0,0 +1,27 @@ + Red and bold + * Bold + * Blinking (not supported everywhere) + * + * Other options can be defined and the defaults provided by Symfony are + * still present. You can read more at the link below: + * + * https://symfony.com/doc/current/console/coloring.html + * + * @param Output $output + * @return mixed + */ + public function describeDemoData(Output $output); +} diff --git a/src/Custard/Scenario.php b/src/Custard/Scenario.php new file mode 100644 index 0000000..1406cf1 --- /dev/null +++ b/src/Custard/Scenario.php @@ -0,0 +1,55 @@ +seedScenario = $seedScenario; + $this->scenarioDescription = new ScenarioDescription(); + $this->name = $name; + } + + /** + * @param OutputInterface $output + * Run the data seeder and describe what happened to $output + */ + public function run(OutputInterface $output) + { + $seedScenario = $this->seedScenario; + $seedScenario($this->scenarioDescription); + $this->scenarioDescription->describe($output); + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } +} \ No newline at end of file diff --git a/src/Custard/ScenarioDataSeeder.php b/src/Custard/ScenarioDataSeeder.php new file mode 100644 index 0000000..45db4a5 --- /dev/null +++ b/src/Custard/ScenarioDataSeeder.php @@ -0,0 +1,47 @@ +getPreRequisiteSeeders() as $seeder){ + $seeder->seedData($output); + } + + foreach ($this->getScenarios() as $scenario) { + $output->writeln(""); + $output->writeln("Scenario ".self::$scenarioCount.": " . $scenario->getName()); + $output->writeln(""); + $scenario->run($output); + self::$scenarioCount++; + } + } + + /** + * @return Scenario[] + */ + abstract function getScenarios(): array; +} \ No newline at end of file diff --git a/src/Custard/ScenarioDescription.php b/src/Custard/ScenarioDescription.php new file mode 100644 index 0000000..f238912 --- /dev/null +++ b/src/Custard/ScenarioDescription.php @@ -0,0 +1,32 @@ +lines[] = $spacer . $line; + return $this; + } + + /** + * @param OutputInterface $output + * Write all oif the lines + */ + public function describe(OutputInterface $output) + { + $output->writeln($this->lines); + } +} \ No newline at end of file diff --git a/src/Custard/SeedDemoDataCommand.php b/src/Custard/SeedDemoDataCommand.php index b45825a..e3aa9d0 100644 --- a/src/Custard/SeedDemoDataCommand.php +++ b/src/Custard/SeedDemoDataCommand.php @@ -4,7 +4,10 @@ use Rhubarb\Stem\Models\Model; use Rhubarb\Stem\Schema\SolutionSchema; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -13,7 +16,11 @@ class SeedDemoDataCommand extends RequiresRepositoryCommand protected function configure() { $this->setName('stem:seed-data') - ->setDescription('Seeds the repositories with demo data'); + ->setDescription('Seeds the repositories with demo data') + ->addOption("list", "l", null, "Lists the seeders available") + ->addOption("obliterate", "o", InputOption::VALUE_NONE, "Obliterate the entire database first") + ->addOption("force", "f", InputOption::VALUE_NONE, "Forces obliteration even if running only a single seeder") + ->addArgument("seeder", InputArgument::OPTIONAL, "The name of the seeder to run, leave out for all"); parent::configure(); } @@ -23,59 +30,131 @@ protected function configure() */ private static $seeders = []; + private static $enableTruncating = true; + protected function executeWithConnection(InputInterface $input, OutputInterface $output) { + $output->getFormatter()->setStyle('bold', new OutputFormatterStyle(null, null, ['bold'])); + $output->getFormatter()->setStyle('blink', new OutputFormatterStyle(null, null, ['blink'])); + $output->getFormatter()->setStyle('critical', new OutputFormatterStyle('red', null, ['bold'])); + + if ($input->getOption("list")!=null){ + + $output->writeln("Listing possible seeders:"); + $output->writeln(""); + + foreach(self::$seeders as $seeder){ + $output->writeln("\t" . basename(str_replace("\\", "/", get_class($seeder)))); + } + + $output->writeln(""); + return; + } + + $chosenSeeder = $input->getArgument("seeder"); + + + if (($input->getOption("obliterate")===true) && (!empty($chosenSeeder))){ + // Running a single seeder after an obliteration makes no sense - this is probably + // a mistake. + if ($input->getOption("force")===false){ + $output->writeln("Running a single seeder after obliteration is probably not sane. Use with -f to force."); + return; + } + } + parent::executeWithConnection($input, $output); - $this->writeNormal("Clearing existing data.", true); + $this->writeNormal("Updating table schemas...", true); $schemas = SolutionSchema::getAllSchemas(); - $modelSchemas = []; - foreach ($schemas as $schema) { - $modelSchemas = array_merge($modelSchemas, $schema->getAllModels()); + $schema->checkModelSchemas(); } - $progressBar = new ProgressBar($output, sizeof($modelSchemas)); + if (($input->getOption("obliterate")===true) || self::$enableTruncating) { - foreach ($modelSchemas as $alias => $modelClass) { - $progressBar->advance(); + $this->writeNormal("Clearing existing data.", true); - /** @var Model $model */ - $model = new $modelClass(); - $schema = $model->getSchema(); - $repository = $model->getRepository(); + $modelSchemas = []; - $this->writeNormal(" Truncating " . str_pad(basename($schema->schemaName), 50, ' ', STR_PAD_RIGHT)); + foreach ($schemas as $schema) { + $modelSchemas = array_merge($modelSchemas, $schema->getAllModels()); + } - $repository->clearRepositoryData(); - } + $progressBar = new ProgressBar($output, sizeof($modelSchemas)); - $this->writeNormal("", true); - $this->writeNormal("", true); + foreach ($modelSchemas as $alias => $modelClass) { + $progressBar->advance(); - $this->writeNormal("Running seed scripts...", true); + /** @var Model $model */ + $model = new $modelClass(); + $schema = $model->getSchema(); + + $repository = $model->getRepository(); - $progressBar->finish(); + $this->writeNormal(" Truncating " . str_pad(basename($schema->schemaName), 50, ' ', STR_PAD_RIGHT)); - $progressBar = new ProgressBar($output, sizeof($modelSchemas)); + $repository->clearRepositoryData(); - foreach (self::$seeders as $seeder) { - $progressBar->advance(); + } - $this->writeNormal(" Processing " . str_pad(basename(str_replace("\\", "/", get_class($seeder))), 50, ' ', STR_PAD_RIGHT)); + $progressBar->finish(); - $seeder->seedData($output); + $this->writeNormal("", true); + $this->writeNormal("", true); } - $progressBar->finish(); + $this->writeNormal("Running seed scripts...", true); + + if ($chosenSeeder){ + $found = false; + foreach (self::$seeders as $seeder) { + if (strtolower(basename(str_replace("\\", "/", get_class($seeder)))) == strtolower($chosenSeeder)){ + $this->writeNormal(" Processing " . str_pad(basename(str_replace("\\", "/", get_class($seeder))), 50, ' ', STR_PAD_RIGHT)); + $output->writeln(['', '']); + + if ($seeder instanceof DescribedDemoDataSeederInterface) { + $seeder->describeDemoData($output); + } + + $seeder->seedData($output); + $found = true; + } + } + + if (!$found){ + $output->writeln("No seeder matching `".$chosenSeeder."`"); + $this->writeNormal("", true); + + return; + } + + } else { + $progressBar = new ProgressBar($output, sizeof(self::$seeders)); + + foreach (self::$seeders as $seeder) { + $progressBar->advance(); + + $this->writeNormal(" Processing " . str_pad(basename(str_replace("\\", "/", get_class($seeder))), 50, ' ', STR_PAD_RIGHT)); + + $seeder->seedData($output); + } + + $progressBar->finish(); + + } - $this->writeNormal("", true); $this->writeNormal("", true); $this->writeNormal("Seeding Complete", true); } + public static function setEnableTruncating($enableTruncating) + { + self::$enableTruncating = $enableTruncating; + } + public static function registerDemoDataSeeder(DemoDataSeederInterface $demoDataSeeder) { self::$seeders[] = $demoDataSeeder; From 01b83a28b2741624c5a878eca3d22f66b1f7783b Mon Sep 17 00:00:00 2001 From: Andrew Cuthbert Date: Fri, 30 Jul 2021 10:50:34 +0100 Subject: [PATCH 16/17] Retro fitted support for scenario data seeding --- src/Custard/BulkScenario.php | 12 +++++++ src/Custard/ScenarioDataSeeder.php | 50 ++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 src/Custard/BulkScenario.php diff --git a/src/Custard/BulkScenario.php b/src/Custard/BulkScenario.php new file mode 100644 index 0000000..48b6f80 --- /dev/null +++ b/src/Custard/BulkScenario.php @@ -0,0 +1,12 @@ +getPreRequisiteSeeders() as $seeder){ - $seeder->seedData($output); + foreach ($this->getPreRequisiteSeeders() as $seeder) { + if (is_string($seeder)) { + $seeder = new $seeder(); + } + + if (!($seeder instanceof DemoDataSeederInterface)) { + throw new \InvalidArgumentException(get_class($seeder) . " does not extend DemoDataSeederInterface."); + } + + $seeder->seedData($output, $includeBulk); } foreach ($this->getScenarios() as $scenario) { - $output->writeln(""); - $output->writeln("Scenario ".self::$scenarioCount.": " . $scenario->getName()); - $output->writeln(""); - $scenario->run($output); - self::$scenarioCount++; + if ($includeBulk || !($scenario instanceof BulkScenario)) { + $this->beforeScenario($scenario); + + $output->writeln(""); + $output->writeln("Scenario " . self::$scenarioCount . ": " . $scenario->getName() . ''); + $output->writeln(str_repeat('-', 11 + strlen(self::$scenarioCount) + strlen($scenario->getName()))); + $scenario->run($output); + self::$scenarioCount++; + + $this->afterScenario($scenario); + } } } @@ -44,4 +74,4 @@ public function seedData(OutputInterface $output) * @return Scenario[] */ abstract function getScenarios(): array; -} \ No newline at end of file +} From 5407a2f0e5e85ac862e18850700bba04f79c34ff Mon Sep 17 00:00:00 2001 From: Andrew Cuthbert Date: Fri, 30 Jul 2021 16:17:45 +0100 Subject: [PATCH 17/17] More updates for seeding --- src/Custard/SeedDemoDataCommand.php | 52 +++++++++++++++++------------ src/Repositories/MySql/MySql.php | 1 - 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/Custard/SeedDemoDataCommand.php b/src/Custard/SeedDemoDataCommand.php index e3aa9d0..e3d85a8 100644 --- a/src/Custard/SeedDemoDataCommand.php +++ b/src/Custard/SeedDemoDataCommand.php @@ -17,10 +17,11 @@ protected function configure() { $this->setName('stem:seed-data') ->setDescription('Seeds the repositories with demo data') - ->addOption("list", "l", null, "Lists the seeders available") - ->addOption("obliterate", "o", InputOption::VALUE_NONE, "Obliterate the entire database first") - ->addOption("force", "f", InputOption::VALUE_NONE, "Forces obliteration even if running only a single seeder") - ->addArgument("seeder", InputArgument::OPTIONAL, "The name of the seeder to run, leave out for all"); + ->addOption("list", "l", null, "Lists the seeders available") + ->addOption("obliterate", "o", InputOption::VALUE_NONE, "Obliterate the entire database first") + ->addOption("bulk", "b", InputOption::VALUE_NONE, "Include bulk seed sets") + ->addOption("force", "f", InputOption::VALUE_NONE, "Forces obliteration even if running only a single seeder") + ->addArgument("seeder", InputArgument::OPTIONAL, "The name of the seeder to run, leave out for all"); parent::configure(); } @@ -30,20 +31,30 @@ protected function configure() */ private static $seeders = []; - private static $enableTruncating = true; + private static $enableTruncating = false; + + /** + * True if we're presently seeding the database + * + * Used to stop event chains based of model chains that the seeder should not cause. + * e.g. user activation emails + */ + public static $seeding = false; protected function executeWithConnection(InputInterface $input, OutputInterface $output) { + SeedDemoDataCommand::$seeding = true; + $output->getFormatter()->setStyle('bold', new OutputFormatterStyle(null, null, ['bold'])); $output->getFormatter()->setStyle('blink', new OutputFormatterStyle(null, null, ['blink'])); $output->getFormatter()->setStyle('critical', new OutputFormatterStyle('red', null, ['bold'])); - if ($input->getOption("list")!=null){ + if ($input->getOption("list") != null) { $output->writeln("Listing possible seeders:"); $output->writeln(""); - foreach(self::$seeders as $seeder){ + foreach (self::$seeders as $seeder) { $output->writeln("\t" . basename(str_replace("\\", "/", get_class($seeder)))); } @@ -53,11 +64,10 @@ protected function executeWithConnection(InputInterface $input, OutputInterface $chosenSeeder = $input->getArgument("seeder"); - - if (($input->getOption("obliterate")===true) && (!empty($chosenSeeder))){ + if (($input->getOption("obliterate") === true) && (!empty($chosenSeeder))) { // Running a single seeder after an obliteration makes no sense - this is probably // a mistake. - if ($input->getOption("force")===false){ + if ($input->getOption("force") === false) { $output->writeln("Running a single seeder after obliteration is probably not sane. Use with -f to force."); return; } @@ -72,7 +82,7 @@ protected function executeWithConnection(InputInterface $input, OutputInterface $schema->checkModelSchemas(); } - if (($input->getOption("obliterate")===true) || self::$enableTruncating) { + if (($input->getOption("obliterate") === true) || self::$enableTruncating) { $this->writeNormal("Clearing existing data.", true); @@ -96,7 +106,6 @@ protected function executeWithConnection(InputInterface $input, OutputInterface $this->writeNormal(" Truncating " . str_pad(basename($schema->schemaName), 50, ' ', STR_PAD_RIGHT)); $repository->clearRepositoryData(); - } $progressBar->finish(); @@ -107,10 +116,12 @@ protected function executeWithConnection(InputInterface $input, OutputInterface $this->writeNormal("Running seed scripts...", true); - if ($chosenSeeder){ + $includeBulk = ($input->getOption("bulk") === true); + + if ($chosenSeeder) { $found = false; foreach (self::$seeders as $seeder) { - if (strtolower(basename(str_replace("\\", "/", get_class($seeder)))) == strtolower($chosenSeeder)){ + if (strtolower(basename(str_replace("\\", "/", get_class($seeder)))) == strtolower($chosenSeeder)) { $this->writeNormal(" Processing " . str_pad(basename(str_replace("\\", "/", get_class($seeder))), 50, ' ', STR_PAD_RIGHT)); $output->writeln(['', '']); @@ -118,18 +129,17 @@ protected function executeWithConnection(InputInterface $input, OutputInterface $seeder->describeDemoData($output); } - $seeder->seedData($output); + $seeder->seedData($output, $includeBulk); $found = true; } } - if (!$found){ - $output->writeln("No seeder matching `".$chosenSeeder."`"); + if (!$found) { + $output->writeln("No seeder matching `" . $chosenSeeder . "`"); $this->writeNormal("", true); return; } - } else { $progressBar = new ProgressBar($output, sizeof(self::$seeders)); @@ -138,16 +148,16 @@ protected function executeWithConnection(InputInterface $input, OutputInterface $this->writeNormal(" Processing " . str_pad(basename(str_replace("\\", "/", get_class($seeder))), 50, ' ', STR_PAD_RIGHT)); - $seeder->seedData($output); + $seeder->seedData($output, $includeBulk); } $progressBar->finish(); - } $this->writeNormal("", true); + $this->writeNormal("Seeding Complete", true); - $this->writeNormal("Seeding Complete", true); + SeedDemoDataCommand::$seeding = false; } public static function setEnableTruncating($enableTruncating) diff --git a/src/Repositories/MySql/MySql.php b/src/Repositories/MySql/MySql.php index b4706f8..2b249d4 100644 --- a/src/Repositories/MySql/MySql.php +++ b/src/Repositories/MySql/MySql.php @@ -252,7 +252,6 @@ public function batchCommitUpdatesFromCollection(Collection $collection, $proper $namedParams[$paramName] = $value; $sets[] = "`" . $key . "` = :" . $paramName; - } $sql = "UPDATE `{$table}` SET " . implode(",", $sets) . $whereClause;