diff --git a/src/Database/Barry/Model.php b/src/Database/Barry/Model.php index 45f476b6..0e07a98a 100644 --- a/src/Database/Barry/Model.php +++ b/src/Database/Barry/Model.php @@ -306,8 +306,11 @@ public static function create(array $data): Model } // Check if the primary key existe on updating data - if (!array_key_exists($model->primary_key, $data)) { - if ($model->auto_increment && static::$builder->getAdapterName() !== "pgsql") { + if ( + !array_key_exists($model->primary_key, $data) + && static::$builder->getAdapterName() !== "pgsql" + ) { + if ($model->auto_increment) { $id_value = [$model->primary_key => null]; $data = array_merge($id_value, $data); } elseif ($model->primary_key_type == 'string') { @@ -319,11 +322,7 @@ public static function create(array $data): Model // Override the olds model attributes $model->setAttributes($data); - - if ($model->save() == 1) { - // Throw the onCreated event - $model->fireEvent('onCreated'); - } + $model->save(); return $model; } @@ -349,7 +348,33 @@ public static function paginate(int $page_number, int $current = 0, ?int $chunk */ public static function deleted(callable $cb): void { - $env = static::formatEventName('onDeleted'); + $env = static::formatEventName('model.deleted'); + + event()->once($env, $cb); + } + + /** + * Allows to associate listener + * + * @param callable $cb + * @throws + */ + public static function deleting(callable $cb): void + { + $env = static::formatEventName('model.deleted'); + + event()->once($env, $cb); + } + + /** + * Allows to associate a listener + * + * @param callable $cb + * @throws + */ + public static function creating(callable $cb): void + { + $env = static::formatEventName('model.creating'); event()->once($env, $cb); } @@ -362,7 +387,20 @@ public static function deleted(callable $cb): void */ public static function created(callable $cb): void { - $env = static::formatEventName('onCreated'); + $env = static::formatEventName('model.created'); + + event()->once($env, $cb); + } + + /** + * Allows to associate a listener + * + * @param callable $cb + * @throws + */ + public static function updating(callable $cb): void + { + $env = static::formatEventName('model.updating'); event()->once($env, $cb); } @@ -375,7 +413,7 @@ public static function created(callable $cb): void */ public static function updated(callable $cb): void { - $env = static::formatEventName('onUpdated'); + $env = static::formatEventName('model.updated'); event()->once($env, $cb); } @@ -432,50 +470,70 @@ public static function query(): Builder } /** - * Save aliases on insert action + * Create the new row * + * @param Builder $model * @return int - * @throws */ - public function save() + private function writeRows(Builder $builder) { - $model = static::query(); + // Fire the creating event + $this->fireEvent('model.creating'); - /** - * Get the current primary key value - */ $primary_key_value = $this->getKeyValue(); - // If primary key value is null, we are going to start the creation of new row - if ($primary_key_value == null) { - // Insert information in the database - $row_affected = $model->insert($this->attributes); + // Insert information in the database + $row_affected = $builder->insert($this->attributes); - // We get a last insertion id value - if (static::$builder->getAdapterName() == 'pgsql') { + // We get a last insertion id value + if (static::$builder->getAdapterName() == 'pgsql') { + if ($this->auto_increment) { $sequence = $this->table . "_" . $this->primary_key . '_seq'; $primary_key_value = static::$builder->getPdo()->lastInsertId($sequence); - } else { - $primary_key_value = static::$builder->getPdo()->lastInsertId(); } + } else { + $primary_key_value = static::$builder->getPdo()->lastInsertId(); + } - $primary_key_value = !is_numeric($primary_key_value) ? $primary_key_value : (int) $primary_key_value; + if (is_null($primary_key_value)) { + $primary_key_value = $this->attributes[$this->primary_key] ?? null; + } - // Set the primary key value - $this->attributes[$this->primary_key] = $primary_key_value; - $this->original = $this->attributes; + $primary_key_value = !is_numeric($primary_key_value) ? $primary_key_value : (int) $primary_key_value; - if ($row_affected == 1) { - $this->fireEvent('onCreated'); - } + // Set the primary key value + $this->attributes[$this->primary_key] = $primary_key_value; + $this->original = $this->attributes; - return $row_affected; + if ($row_affected == 1) { + $this->fireEvent('model.created'); + } + + return $row_affected; + } + + /** + * Save aliases on insert action + * + * @return int + * @throws + */ + public function save() + { + $builder = static::query(); + + // Get the current primary key value + $primary_key_value = $this->getKeyValue(); + + // If primary key value is null, we are going to start the creation of new row + if (is_null($primary_key_value)) { + return $this->writeRows($builder); } $primary_key_value = $this->transtypeKeyValue($primary_key_value); // Check the existent in database - if (!$model->exists($this->primary_key, $primary_key_value)) { + if (!$builder->exists($this->primary_key, $primary_key_value)) { return 0; } @@ -495,11 +553,15 @@ public function save() $update_data = $this->original; } - $updated = $model->where($this->primary_key, $primary_key_value)->update($update_data); + // Fire the updating event + $this->fireEvent('model.updating'); + + // Execute update model + $updated = $builder->where($this->primary_key, $primary_key_value)->update($update_data); // Fire the updated event if there are affected row if ($updated) { - $this->fireEvent('onUpdated'); + $this->fireEvent('model.updated'); } return $updated; @@ -515,16 +577,14 @@ private function transtypeKeyValue(mixed $primary_key_value): mixed { // Transtype value to the define primary key type if ($this->primary_key_type == 'int') { - $primary_key_value = (int) $primary_key_value; - } elseif ($this->primary_key_type == 'float') { - $primary_key_value = (float) $primary_key_value; - } elseif ($this->primary_key_type == 'double') { - $primary_key_value = (float) $primary_key_value; - } else { - $primary_key_value = (string) $primary_key_value; + return (int) $primary_key_value; } - return $primary_key_value; + if ($this->primary_key_type == 'float' || $this->primary_key_type == 'double') { + return (float) $primary_key_value; + } + + return (string) $primary_key_value; } /** @@ -538,8 +598,9 @@ public function update(array $attributes): int { $primary_key_value = $this->getKeyValue(); + // return 0 if the primary key is not define for update if ($primary_key_value == null) { - return 0; + return false; } $model = static::query(); @@ -558,9 +619,12 @@ public function update(array $attributes): int } } + // Fire the updating event + $this->fireEvent('model.updating'); + // When the data for updating list is empty, we load the original data if (count($data_for_updating) == 0) { - $this->fireEvent('onUpdated'); + $this->fireEvent('model.updated'); return true; } @@ -569,7 +633,7 @@ public function update(array $attributes): int // Fire the updated event if there are affected row if ($updated) { - $this->fireEvent('onUpdated'); + $this->fireEvent('model.updated'); } return $updated; @@ -595,12 +659,15 @@ public function delete(): int return 0; } + // Fire the deleting event + $this->fireEvent('model.deleting'); + // We apply the delete action $deleted = $model->where($this->primary_key, $primary_key_value)->delete(); // Fire the deleted event if there are affected row if ($deleted) { - $this->fireEvent('onDeleted'); + $this->fireEvent('model.deleted'); } return $deleted; @@ -642,6 +709,16 @@ public function getKey(): string return $this->primary_key; } + /** + * Retrieves the primary key key + * + * @return string + */ + public function getKeyType(): string + { + return $this->primary_key_type; + } + /** * Used to update the timestamp. * diff --git a/src/Database/Barry/Traits/EventTrait.php b/src/Database/Barry/Traits/EventTrait.php index 895fea23..9b535673 100644 --- a/src/Database/Barry/Traits/EventTrait.php +++ b/src/Database/Barry/Traits/EventTrait.php @@ -16,7 +16,9 @@ trait EventTrait */ private static function formatEventName(string $event): string { - return str_replace('\\', '', strtolower(Str::snake(static::class))) . '.' . Str::snake($event); + $class_name = str_replace('\\', '', strtolower(Str::snake(static::class))); + + return sprintf("%s.%s", $class_name, strtolower($event)); } /** @@ -26,7 +28,7 @@ private static function formatEventName(string $event): string */ private function fireEvent(string $event): void { - $env = $this->formatEventName($event); + $env = static::formatEventName($event); if (event()->bound($env)) { event()->emit($env, $this); diff --git a/src/Database/Connection/Adapter/PostgreSQLAdapter.php b/src/Database/Connection/Adapter/PostgreSQLAdapter.php index 5ad0c8eb..6e92cfe7 100644 --- a/src/Database/Connection/Adapter/PostgreSQLAdapter.php +++ b/src/Database/Connection/Adapter/PostgreSQLAdapter.php @@ -60,6 +60,30 @@ public function connection(): void // Formatting connection parameters $dsn = sprintf("pgsql:host=%s;port=%s;dbname=%s", $hostname, $port, $this->config['database']); + if (isset($this->config['sslmode'])) { + $dsn .= ';sslmode=' . $this->config['sslmode']; + } + + if (isset($this->config['sslrootcert'])) { + $dsn .= ';sslrootcert=' . $this->config['sslrootcert']; + } + + if (isset($this->config['sslcert'])) { + $dsn .= ';sslcert=' . $this->config['sslcert']; + } + + if (isset($this->config['sslkey'])) { + $dsn .= ';sslkey=' . $this->config['sslkey']; + } + + if (isset($this->config['sslcrl'])) { + $dsn .= ';sslcrl=' . $this->config['sslcrl']; + } + + if (isset($this->config['application_name'])) { + $dsn .= ';application_name=' . $this->config['application_name']; + } + $username = $this->config["username"]; $password = $this->config["password"]; @@ -71,5 +95,9 @@ public function connection(): void // Build the PDO connection $this->pdo = new PDO($dsn, $username, $password, $options); + + if ($this->config["charset"]) { + $this->pdo->query('SET NAMES \'' . $this->config["charset"] . '\''); + } } } diff --git a/src/Database/Migration/Compose/PgsqlCompose.php b/src/Database/Migration/Compose/PgsqlCompose.php index 74ecaba5..35473c6b 100644 --- a/src/Database/Migration/Compose/PgsqlCompose.php +++ b/src/Database/Migration/Compose/PgsqlCompose.php @@ -6,14 +6,19 @@ trait PgsqlCompose { + /** + * Define the query for create the custom type + * + * @var array + */ protected array $custom_types = []; /** - * Generate the custom type for pgsql + * Get the custom type for pgsql * * @return array */ - public function generateCustomTypes(): array + public function getCustomTypeQueries(): array { return $this->custom_types; } @@ -94,7 +99,7 @@ private function composeAddPgsqlColumn(string $name, array $description): string // Bind auto increment action if ($increment) { - $type = 'SERIAL'; + $type = in_array($raw_type, ["INT", "TINYINT", "SMALLINT"]) ? "SERIAL" : "BIGSERIAL"; } // Set column as primary key diff --git a/src/Database/Migration/Migration.php b/src/Database/Migration/Migration.php index 8b8d7903..e07bb024 100644 --- a/src/Database/Migration/Migration.php +++ b/src/Database/Migration/Migration.php @@ -119,7 +119,7 @@ final public function create(string $table, callable $cb): Migration return $this->executeSqlQuery($sql); } - foreach ($generator->generateCustomTypes() as $sql) { + foreach ($generator->getCustomTypeQueries() as $sql) { try { $this->executeSqlQuery($sql); } catch (\Exception $exception) { diff --git a/src/Database/QueryBuilder.php b/src/Database/QueryBuilder.php index 05bea7b3..97a55c09 100644 --- a/src/Database/QueryBuilder.php +++ b/src/Database/QueryBuilder.php @@ -1171,7 +1171,7 @@ public function drop(): bool * @param int $chunk * @return array */ - public function paginate(int $number_of_page, int $current = 0, int $chunk = null): array + public function paginate(int $number_of_page, int $current = 0, ?int $chunk = null): array { // We go to back page --$current; diff --git a/src/Translate/README.md b/src/Translate/README.md index ab5824bf..4c826aaa 100644 --- a/src/Translate/README.md +++ b/src/Translate/README.md @@ -1,8 +1,8 @@ # Bow Translate -Bow Framework's translate system is a very simple translate api. He support plurialize. +Bow Framework's translation system is a very simple translation API. Also supports pluralization. -This the sample configuration +This is the sample configuration ```php // frontend/lang/en/messages.php @@ -11,14 +11,12 @@ return [ ]; ``` -Let's show a little exemple: +Let's show a little example: ```php use Bow\Translate\Translator; echo Translator::translate('messages.welcome'); - // Or - echo trans('messages.welcome'); ``` diff --git a/src/Translate/TranslatorConfiguration.php b/src/Translate/TranslatorConfiguration.php index c198928d..cdf28e25 100644 --- a/src/Translate/TranslatorConfiguration.php +++ b/src/Translate/TranslatorConfiguration.php @@ -15,15 +15,20 @@ class TranslatorConfiguration extends Configuration public function create(Loader $config): void { $this->container->bind('translate', function () use ($config) { - $auto_detected = is_null($config['translate.auto_detected']) - ? false - : $config['translate.auto_detected']; + $auto_detected = $config['translate.auto_detected'] ?? false; + $lang = $config['translate.lang']; + $dictionary = $config['translate.dictionary']; - return Translator::configure( - $config['translate.lang'], - $config['translate.dictionary'], - $auto_detected - ); + if ($auto_detected) { + $lang = app("request")->lang(); + if (is_string($lang)) { + $lang = strtolower($lang); + } else { + $lang = $config['translate.lang']; + } + } + + return Translator::configure($lang, $dictionary, $auto_detected); }); } diff --git a/src/Validation/FieldLexical.php b/src/Validation/FieldLexical.php index 223cddf2..50d68bb3 100644 --- a/src/Validation/FieldLexical.php +++ b/src/Validation/FieldLexical.php @@ -10,48 +10,69 @@ trait FieldLexical * Get error debugging information * * @param string $key - * @param string|array $attributes + * @param string|array $value * @return ?string */ - private function lexical(string $key, string|array $attributes): ?string + private function lexical(string $key, string|array $value): ?string { - if (is_string($attributes) && isset($this->messages[$attributes])) { - return $this->messages[$attributes][$key] ?? $this->messages[$attributes]; - } + $data = array_merge( + $this->inputs ?? [], + is_array($value) ? $value : ['attribute' => $value] + ); + + if (is_array($value) && isset($value['attribute'])) { + $message = $this->messages[$value['attribute']][$key] ?? $this->messages[$value['attribute']] ?? null; + + if (is_string($message)) { + return $this->parseAttribute($data, $message); + } - if ( - is_array($attributes) - && isset($attributes['attribute']) - && isset($this->messages[$attributes['attribute']]) - ) { - return $this->messages[$attributes['attribute']][$key] ?? $this->messages[$attributes['attribute']]; + if (is_null($message)) { + return $this->parseFromTranslate($key, $data); + } } - if (is_string($attributes)) { - $attributes = ['attribute' => $attributes]; + if (is_string($value) && isset($this->messages[$value])) { + $message = $this->messages[$value][$key] ?? $this->messages[$value]; + + if (is_string($message)) { + return $this->parseAttribute($data, $message); + } } + return $this->parseFromTranslate($key, $data); + } + + /** + * Parse the translate content + * + * @param string $key + * @param array $data + * @return string + */ + private function parseFromTranslate(string $key, array $data) + { // Get lexical provided by dev app - $lexical = trans('validation.' . $key, $attributes); + $message = trans('validation.' . $key, $data); - if (is_null($lexical)) { - $lexical = $this->parseAttribute($attributes, $this->lexical[$key]); + if (is_null($message)) { + $message = $this->lexical[$key]; } - return $lexical; + return $this->parseAttribute($data, $message); } /** * Normalize beneficiaries * - * @param array $attributes + * @param array $attribute * @param string $lexical * @return string */ - private function parseAttribute(array $attributes, string $lexical): ?string + private function parseAttribute(array $attribute, string $lexical): ?string { - foreach ($attributes as $key => $value) { - $lexical = str_replace('{' . $key . '}', $value, $lexical); + foreach ($attribute as $key => $value) { + $lexical = str_replace('{' . $key . '}', (string) $value, $lexical); } return $lexical; diff --git a/src/Validation/stubs/lexical.php b/src/Validation/stubs/lexical.php index ae786e4c..b0fc4fb2 100644 --- a/src/Validation/stubs/lexical.php +++ b/src/Validation/stubs/lexical.php @@ -1,25 +1,25 @@ 'The field {attribute} must be an email.', - 'required' => 'The field {attribute} is required.', - 'empty' => 'The field {attribute} is missing in the fields to be validated.', - 'min' => 'The field {attribute} must be at least {length} characters long.', - 'max' => 'The field {attribute} must not exceed {length} characters.', - 'same' => 'The field {attribute} must be the same as {value}.', - 'number' => 'The field {attribute} must be a number.', - 'int' => 'The field {attribute} must be an integer.', - 'float' => 'The field {attribute} must be a decimal.', + 'email' => 'The {attribute} field must be an email.', + 'required' => 'The {attribute} field is required.', + 'empty' => 'The {attribute} field is missing in the fields to be validated.', + 'min' => 'The {attribute} field must be at least {length} characters long.', + 'max' => 'The {attribute} field must not exceed {length} characters.', + 'same' => 'The {attribute} field must be the same as {value}.', + 'number' => 'The {attribute} field must be a number.', + 'int' => 'The {attribute} field must be an integer.', + 'float' => 'The {attribute} field must be a decimal.', 'alphanum' => 'Only alphanumeric characters are allowed for field {attribute}.', - 'in' => 'The field {attribute} must be one of the following {value}.', - 'size' => 'The field {attribute} must be {length} characters long.', + 'in' => 'The {attribute} field must be one of the following {value}.', + 'size' => 'The {attribute} field must be {length} characters long.', 'lower' => 'Only lowercase letters are allowed for field {attribute}.', 'upper' => 'Only uppercase letters are allowed for field {attribute}.', 'alpha' => 'Only alphabetic characters are allowed for field {attribute}.', - 'exists' => 'The field {attribute} does not exists.', - 'not_exists' => 'The field {attribute} already exists.', - 'unique' => 'The field {attribute} must be unique.', - 'date' => 'The field {attribute} must use the format: yyyy-mm-dd', - 'datetime' => 'The field {attribute} must use the format: yyyy-mm-dd hh:mm:ss', - 'regex' => 'The field {attribute} does not match the pattern', + 'exists' => 'The {attribute} field does not exists.', + 'not_exists' => 'The {attribute} field already exists.', + 'unique' => 'The {attribute} field must be unique.', + 'date' => 'The {attribute} field must use the format: yyyy-mm-dd', + 'datetime' => 'The {attribute} field must use the format: yyyy-mm-dd hh:mm:ss', + 'regex' => 'The {attribute} field does not match the pattern', ]; diff --git a/tests/Config/stubs/translate.php b/tests/Config/stubs/translate.php new file mode 100644 index 00000000..38c282b8 --- /dev/null +++ b/tests/Config/stubs/translate.php @@ -0,0 +1,19 @@ + 'fr', + + /** + * When the value is true, the translation system + * will detect the language of the client and will translate according to + */ + 'auto_detected' => false, + + /** + * Path to the language repeater + */ + 'dictionary' => __DIR__ . '/../../Translate/stubs', +]; diff --git a/tests/Database/Migration/Pgsql/SQLGenetorHelpersTest.php b/tests/Database/Migration/Pgsql/SQLGenetorHelpersTest.php index 67cd22e9..ab046d86 100644 --- a/tests/Database/Migration/Pgsql/SQLGenetorHelpersTest.php +++ b/tests/Database/Migration/Pgsql/SQLGenetorHelpersTest.php @@ -189,6 +189,7 @@ public function test_change_string_without_size_sql_statement(string $type, stri public function test_add_int_sql_statement(string $type, string $method, int|string $default = 1) { $type = strtoupper($type); + $serial = in_array($type, ["INT", "TINYINT", "SMALLINT"]) ? "SERIAL" : "BIGSERIAL"; $sql = $this->generator->{"add$method"}('column')->make(); $this->assertEquals($sql, "\"column\" {$type} NOT NULL"); @@ -206,7 +207,7 @@ public function test_add_int_sql_statement(string $type, string $method, int|str $this->assertEquals($sql, "\"column\" {$type} PRIMARY KEY NULL DEFAULT $default"); $sql = $this->generator->{"add$method"}('column', ['primary' => true, 'increment' => true, 'size' => 100, 'nullable' => true])->make(); - $this->assertEquals($sql, "\"column\" SERIAL PRIMARY KEY NULL"); + $this->assertEquals($sql, "\"column\" $serial PRIMARY KEY NULL"); $sql = $this->generator->{"add$method"}('column', ['unique' => true])->make(); $this->assertEquals($sql, "\"column\" {$type} UNIQUE NOT NULL"); @@ -214,7 +215,7 @@ public function test_add_int_sql_statement(string $type, string $method, int|str $method = "add{$method}Increment"; if (method_exists($this->generator, $method)) { $sql = $this->generator->{$method}('column')->make(); - $this->assertEquals($sql, "\"column\" SERIAL PRIMARY KEY NOT NULL"); + $this->assertEquals($sql, "\"column\" $serial PRIMARY KEY NOT NULL"); } } @@ -224,6 +225,7 @@ public function test_add_int_sql_statement(string $type, string $method, int|str public function test_change_int_sql_statement(string $type, string $method, int|string $default = 1) { $type = strtoupper($type); + $serial = in_array($type, ["INT", "TINYINT", "SMALLINT"]) ? "SERIAL" : "BIGSERIAL"; $sql = $this->generator->{"change$method"}('column')->make(); $this->assertEquals($sql, "MODIFY COLUMN \"column\" {$type} NOT NULL"); @@ -241,7 +243,7 @@ public function test_change_int_sql_statement(string $type, string $method, int| $this->assertEquals($sql, "MODIFY COLUMN \"column\" {$type} PRIMARY KEY NULL DEFAULT $default"); $sql = $this->generator->{"change$method"}('column', ['primary' => true, 'increment' => true, 'size' => 100, 'nullable' => true])->make(); - $this->assertEquals($sql, "MODIFY COLUMN \"column\" SERIAL PRIMARY KEY NULL"); + $this->assertEquals($sql, "MODIFY COLUMN \"column\" $serial PRIMARY KEY NULL"); $sql = $this->generator->{"change$method"}('column', ['unique' => true])->make(); $this->assertEquals($sql, "MODIFY COLUMN \"column\" {$type} UNIQUE NOT NULL"); @@ -249,7 +251,7 @@ public function test_change_int_sql_statement(string $type, string $method, int| $method = "change{$method}Increment"; if (method_exists($this->generator, $method)) { $sql = $this->generator->{$method}('column')->make(); - $this->assertEquals($sql, "MODIFY COLUMN \"column\" {$type} SERIAL PRIMARY KEY NOT NULL"); + $this->assertEquals($sql, "MODIFY COLUMN \"column\" {$type} $serial PRIMARY KEY NOT NULL"); } } diff --git a/tests/Translate/TranslationTest.php b/tests/Translate/TranslationTest.php index 5b10b64c..4f3f8a18 100644 --- a/tests/Translate/TranslationTest.php +++ b/tests/Translate/TranslationTest.php @@ -3,12 +3,14 @@ namespace Bow\Tests\Translate; use Bow\Translate\Translator; +use Bow\Tests\Config\TestingConfiguration; class TranslationTest extends \PHPUnit\Framework\TestCase { public static function setUpBeforeClass(): void { - Translator::configure('fr', __DIR__ . '/stubs'); + $config = TestingConfiguration::getConfig(); + Translator::configure($config['translate.lang'], $config["translate.dictionary"]); } public function test_fr_welcome_message() diff --git a/tests/Validation/ValidationTest.php b/tests/Validation/ValidationTest.php index bee5cba4..b9b69f54 100644 --- a/tests/Validation/ValidationTest.php +++ b/tests/Validation/ValidationTest.php @@ -3,21 +3,17 @@ namespace Bow\Tests\Validation; use Bow\Database\Database; -use Bow\Tests\Config\TestingConfiguration; +use Bow\Translate\Translator; use Bow\Validation\Validator; +use Bow\Tests\Config\TestingConfiguration; class ValidationTest extends \PHPUnit\Framework\TestCase { - private static Database $database; - public static function setUpBeforeClass(): void { - static::$database = Database::getInstance(); - - if (!static::$database) { - $configuration = TestingConfiguration::getConfig(); - Database::configure($configuration["database"]); - } + $config = TestingConfiguration::getConfig(); + Database::configure($config["database"]); + Translator::configure($config['translate.lang'], $config["translate.dictionary"]); Database::statement("create table if not exists pets (id int primary key, name varchar(225));"); Database::table("pets")->truncate();