Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add method to insert empty data in Model #6109

Merged
merged 7 commits into from
Jun 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions system/BaseModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,11 @@ abstract class BaseModel
*/
protected $afterDelete = [];

/**
* Whether to allow inserting empty data.
*/
protected bool $allowEmptyInserts = false;

public function __construct(?ValidationInterface $validation = null)
{
$this->tempReturnType = $this->returnType;
Expand Down Expand Up @@ -742,7 +747,7 @@ public function insert($data = null, bool $returnID = true)

// doProtectFields() can further remove elements from
// $data so we need to check for empty dataset again
if (empty($data)) {
if (! $this->allowEmptyInserts && empty($data)) {
throw DataException::forEmptyDataset('insert');
}

Expand Down Expand Up @@ -1640,7 +1645,7 @@ protected function transformDataToArray($data, string $type): array
throw new InvalidArgumentException(sprintf('Invalid type "%s" used upon transforming data to array.', $type));
}

if (empty($data)) {
if (! $this->allowEmptyInserts && empty($data)) {
throw DataException::forEmptyDataset($type);
}

Expand All @@ -1659,7 +1664,7 @@ protected function transformDataToArray($data, string $type): array
}

// If it's still empty here, means $data is no change or is empty object
if (empty($data)) {
if (! $this->allowEmptyInserts && empty($data)) {
throw DataException::forEmptyDataset($type);
}

Expand Down Expand Up @@ -1765,4 +1770,14 @@ protected function fillPlaceholders(array $rules, array $data): array

return $rules;
}

/**
* Sets $allowEmptyInserts.
*/
public function allowEmptyInserts(bool $value = true): self
{
$this->allowEmptyInserts = $value;

return $this;
}
}
29 changes: 28 additions & 1 deletion system/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,34 @@ protected function doInsert(array $data)
$builder->set($key, $val, $escape[$key] ?? null);
}

$result = $builder->insert();
if ($this->allowEmptyInserts && empty($data)) {
$table = $this->db->protectIdentifiers($this->table, true, null, false);
if ($this->db->getPlatform() === 'MySQLi') {
$sql = 'INSERT INTO ' . $table . ' VALUES ()';
} elseif ($this->db->getPlatform() === 'OCI8') {
$allFields = $this->db->protectIdentifiers(
array_map(
static fn ($row) => $row->name,
$this->db->getFieldData($this->table)
),
false,
true
);

$sql = sprintf(
'INSERT INTO %s (%s) VALUES (%s)',
$table,
implode(',', $allFields),
substr(str_repeat(',DEFAULT', count($allFields)), 1)
);
} else {
$sql = 'INSERT INTO ' . $table . ' DEFAULT VALUES';
MGatner marked this conversation as resolved.
Show resolved Hide resolved
}

$result = $this->db->query($sql);
} else {
$result = $builder->insert();
}

// If insertion succeeded then save the insert ID
if ($result) {
Expand Down
2 changes: 1 addition & 1 deletion system/Validation/FileRules.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public function is_image(?string $blank, string $params): bool

// We know that our mimes list always has the first mime
// start with `image` even when then are multiple accepted types.
$type = Mimes::guessTypeFromExtension($file->getExtension());
$type = Mimes::guessTypeFromExtension($file->getExtension()) ?? '';

if (mb_strpos($type, 'image') !== 0) {
return false;
Expand Down
Empty file.
31 changes: 31 additions & 0 deletions tests/system/Models/InsertModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use CodeIgniter\Database\Exceptions\DataException;
use CodeIgniter\Entity\Entity;
use CodeIgniter\I18n\Time;
use CodeIgniter\Model;
use Config\Database;
use stdClass;
use Tests\Support\Entity\User;
use Tests\Support\Models\JobModel;
Expand Down Expand Up @@ -202,6 +204,35 @@ public function testInsertArrayWithNoDataException(): void
$this->createModel(UserModel::class)->insert([]);
}

public function testInsertPermitInsertNoData(): void
{
$forge = Database::forge();
$forge->addField([
'id' => ['type' => 'INTEGER', 'constraint' => 11, 'auto_increment' => true],
'created_at' => ['type' => 'INTEGER', 'constraint' => 11, 'null' => true],
'updated_at' => ['type' => 'INTEGER', 'constraint' => 11, 'null' => true],
])->addKey('id', true)->createTable('insert_no_data', true);

$model = new class () extends Model {
protected $table = 'insert_no_data';
protected $allowedFields = [
'updated_at',
];
};

$model->allowEmptyInserts()->insert([]);

$this->seeInDatabase('insert_no_data', ['id' => $model->getInsertID()]);

$forge->dropTable('insert_no_data');

$this->expectException(DataException::class);
$this->expectExceptionMessage('There is no data to insert.');

$model->allowEmptyInserts(false);
$model->insert([]);
}

public function testInsertObjectWithNoDataException(): void
{
$data = new stdClass();
Expand Down
3 changes: 3 additions & 0 deletions tests/system/Validation/FileRulesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ final class FileRulesTest extends CIUnitTestCase

protected function setUp(): void
{
$this->resetServices();
parent::setUp();

$this->validation = new Validation((object) $this->config, Services::renderer());
$this->validation->reset();

Expand Down Expand Up @@ -229,6 +231,7 @@ public function testIsntImage(): void
'type' => 'application/address',
'error' => UPLOAD_ERR_OK,
];

$this->validation->setRules(['avatar' => 'is_image[stuff]']);
$this->assertFalse($this->validation->run([]));
}
Expand Down
3 changes: 3 additions & 0 deletions tests/system/Validation/StrictRules/FileRulesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ final class FileRulesTest extends CIUnitTestCase

protected function setUp(): void
{
$this->resetServices();
parent::setUp();

$this->validation = new Validation((object) $this->config, Services::renderer());
$this->validation->reset();

Expand Down Expand Up @@ -230,6 +232,7 @@ public function testIsntImage(): void
'type' => 'application/address',
'error' => UPLOAD_ERR_OK,
];

$this->validation->setRules(['avatar' => 'is_image[stuff]']);
$this->assertFalse($this->validation->run([]));
}
Expand Down
7 changes: 4 additions & 3 deletions user_guide_src/source/changelogs/v4.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Highlights
**********

- Update minimal PHP requirement to 7.4.
- To make the default configuration more secure, auto-routing has been changed to disabled by default.
- **OCI8 Driver for Oracle Database** (*contributed by* `ytetsuro <https://github.com/ytetsuro>`_). See `Database`_.
- **Improved Auto Routing** (opt-in) (*contributed by* `kenjis <https://github.com/kenjis>`_). See `New Improved Auto Routing`_.
- Query Builder **Subqueries** and **UNION** support (*contributed by* `Andrey Pyzhikov <https://github.com/iRedds>`_). See `Database`_.
Expand Down Expand Up @@ -134,11 +135,11 @@ Changes
*******

- Update minimal PHP requirement to 7.4.
- The current version of Content Security Policy (CSP) outputs one nonce for script and one for style tags. The previous version outputted one nonce for each tag.
- The process of sending cookies has been moved to the ``Response`` class. Now the ``Session`` class doesn't send cookies, set them to the Response.
- To make the default configuration more secure, auto-routing has been changed to disabled by default.
- Validation. Changed generation of errors when using fields with a wildcard (*). Now the error key contains the full path. See :ref:`validation-getting-all-errors`.
- ``Validation::getError()`` when using a wildcard will return all found errors matching the mask as a string.
- To make the default configuration more secure, auto-routing has been changed to disabled by default.
- The current version of Content Security Policy (CSP) outputs one nonce for script and one for style tags. The previous version outputted one nonce for each tag.
- The process of sending cookies has been moved to the ``Response`` class. Now the ``Session`` class doesn't send cookies, set them to the Response.

Deprecations
************
Expand Down
1 change: 1 addition & 0 deletions user_guide_src/source/changelogs/v4.3.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Enhancements

- Added the ``StreamFilterTrait`` to make it easier to work with capturing data from STDOUT and STDERR streams. See :ref:`testing-overview-stream-filters`.
- Added before and after events to ``BaseModel::insertBatch()`` and ``BaseModel::updateBatch()`` methods. See :ref:`model-events-callbacks`.
- Added ``Model::allowEmptyInserts()`` method to insert empty data. See :ref:`Using CodeIgniter's Model <model-allow-empty-inserts>`
- Added ``$routes->useSupportedLocalesOnly(true)`` so that the Router returns 404 Not Found if the locale in the URL is not supported in ``Config\App::$supportedLocales``. See :ref:`Localization <localization-in-routes>`
- The call handler for Spark commands from the ``CodeIgniter\CodeIgniter`` class has been extracted. This will reduce the cost of console calls.

Expand Down
11 changes: 11 additions & 0 deletions user_guide_src/source/models/model.rst
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,17 @@ the array's values are the values to save for that key:

You can retrieve the last inserted row's primary key using the ``getInsertID()`` method.

.. _model-allow-empty-inserts:

allowEmptyInserts()
-------------------

Since v4.3.0, you can use ``allowEmptyInserts()`` method to insert empty data. The Model throws an exception when you try to insert empty data by default. But if you call this method, the check will no longer be performed.

.. literalinclude:: model/056.php

You can enable the check again by calling ``allowEmptyInserts(false)``.

update()
--------

Expand Down
3 changes: 3 additions & 0 deletions user_guide_src/source/models/model/056.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

$userModel->allowEmptyInserts()->insert([]);