Skip to content

Commit 2ccc207

Browse files
committed
Insert tests
1 parent 03406da commit 2ccc207

32 files changed

+336
-156
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ vendor/
33
bin/
44
composer.lock
55
.php-cs-fixer.cache
6-
.phpunit.result.cache
6+
.phpunit.cache
77
phpunit.xml
88

README.md

+16-14
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1-
# PHPFUI\ORM [![Tests](https://github.com/phpfui/ORM/actions/workflows/tests.yml/badge.svg)](https://github.com/phpfui/ORM/actions?query=workflow%3Atests) [![Latest Packagist release](https://img.shields.io/packagist/v/phpfui/ORM.svg)](https://packagist.org/packages/phpfui/ORM) ![](https://img.shields.io/badge/PHPStan-level%206-brightgreen.svg?style=flat)
1+
# PHPFUI\ORM [![Tests](https://github.com/phpfui/ORM/actions/workflows/tests.yml/badge.svg)](https://github.com/phpfui/ORM/actions?query=workflow%3Atests) [![Latest Packagist release](https://img.shields.io/packagist/v/phpfui/ORM.svg)](https://packagist.org/packages/phpfui/ORM) ![](https://img.shields.io/badge/PHPStan-level%205-brightgreen.svg?style=flat)
22

33
### PHPFUI\ORM a minimal Object Relational Mapper (ORM) for MySQL, MariaDB and SQLite3
44
Why another PHP ORM? In writing minimal and fast websites, it was determined that existing PHP ORM solutions were overly complex. **PHPFUI\ORM** is less than 6k lines of code in under 50 files. It is designed to have a minimal memory footprint and excellent execution times for most database needs.
55

66
**PHPFUI\ORM** is not an attempt to write an abstraction around SQL as other ORMs do, rather it is a way to work with SQL that closely matches the semantics of SQL, with the power of PHP objects. It allows PHP to manipulate SQL queries without having to write SQL in plain text. This is very useful for queries generated via user interfaces where the user is given a lot of flexability in how a query is defined.
77

88
## Features
9-
- **Active Records** that present a fully type checked object interface and implement basic CRUD functionality.
10-
- **Active Tables** for full table operations including support for where, having, limits, ordering, grouping, joins and unions.
11-
- **Data Cursors** that implement **iterable** and **Countable** eliminating the need for full arrays read into memory.
12-
- **Validation** for fully customizable and translatable backend validation.
13-
- **Virtual Fields** for get and set semantics.
14-
- **Migrations** simple migrations offer atomic up and down migrations.
15-
- **Relations** for parent, children, one to one, many to many, and custom relationships.
16-
- **Type Safe** to prevent stupid type errors.
17-
- **Raw SQL Query Support** execute any valid SQL command.
18-
- **MultiDatabase Support** built on PDO with support for MySQL, MariaDB and SQLite.
9+
- **Active Records** A fully type checked object interface and implement basic CRUD functionality.
10+
- **Active Tables** Full table operations including support for where, having, limits, ordering, grouping, joins and unions.
11+
- **Data Cursors** Cursors implement **iterable** and **Countable** eliminating the need for full arrays read into memory.
12+
- **Validation** Fully customizable and translatable backend validation.
13+
- **Virtual Fields** Supports get and set semantics for any custom or calculated field.
14+
- **Migrations** Simple migrations offer atomic up and down migrations.
15+
- **Relations** Parent, children, one to one, many to many, and custom relationships.
16+
- **Transactions** Object based transaction meaning exceptions can not leave an open transacton.
17+
- **Type Safe** Prevent stupid type errors.
18+
- **Raw SQL Query Support** Execute any valid SQL command.
19+
- **Multiple Database Support** Work with multiple databases simultaneously.
20+
- **Multi-Vendor Support** Built on PDO with support for MySQL, MariaDB and SQLite.
1921

2022
## Usage
2123
### Setup
@@ -108,13 +110,12 @@ Exceptions are generated in the following conditions:
108110
- Incorrect type for Operator (must be an array for **IN** for example)
109111
- Passing an incorrect type as a primary key
110112
- Invalid join type
111-
- Requesting a **RecordCursor** with a table join
112113
- Joining on an invalid table
113114

114115
All of the above exceptions are programmer errors and strictly enforced. Empty queries are not considered errors. SQL may also return [Exceptions](https://www.php.net/manual/en/class.exception.php) if invalid fields are used.
115116

116117
### Type Conversions
117-
If you set a field to the wrong type, the library logs an error then converts the type via the appropriate PHP cast.
118+
If you set a field to the wrong type, the library logs an warning then converts the type via the appropriate PHP cast.
118119

119120
## Multiple Database Support
120121
While this is primarily a single database ORM, you can switch databases at run time. Save the value from `$connectionId = \PHPFUI\ORM::addConnection($pdo);` and then call `\PHPFUI\ORM::useConnection($db);` to switch. `\PHPFUI\ORM::addConnection` will set the current connection.
@@ -155,7 +156,8 @@ foreach ($cursors as $cursor)
155156
+ [Migrations](<https://github.com/phpfui/ORM/blob/main/docs/6. Migrations.md>)
156157
+ [Validation](<https://github.com/phpfui/ORM/blob/main/docs/7. Validation.md>)
157158
+ [Translations](<https://github.com/phpfui/ORM/blob/main/docs/8. Translations.md>)
158-
+ [Miscellaneous](<https://github.com/phpfui/ORM/blob/main/docs/9. Miscellaneous.md>)
159+
+ [Transactions](<https://github.com/phpfui/ORM/blob/main/docs/9. Transactions.md>)
160+
+ [Miscellaneous](<https://github.com/phpfui/ORM/blob/main/docs/10. Miscellaneous.md>)
159161

160162
## Full Class Documentation
161163
[PHPFUI/ORM](http://phpfui.com/?n=PHPFUI\ORM)

Tests/Unit/DeleteTest.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,26 @@ public function testRecordDelete() : void
88
{
99
$table = new \Tests\App\Table\Customer();
1010
$this->assertEquals(29, $table->count());
11-
$this->assertTrue(\PHPFUI\ORM::beginTransaction());
11+
$transaction = new \PHPFUI\ORM\Transaction();
1212
$customer = new \Tests\App\Record\Customer(9);
1313
$this->assertEquals('Company I', $customer->company);
1414
$customer->delete();
1515
$this->assertEquals(28, $table->count());
16-
$this->assertTrue(\PHPFUI\ORM::rollBack());
16+
$this->assertTrue($transaction->rollBack());
1717
$this->assertEquals(29, $table->count());
1818
}
1919

2020
public function testTableDelete() : void
2121
{
2222
$table = new \Tests\App\Table\Customer();
2323
$this->assertEquals(29, $table->count());
24-
$this->assertTrue(\PHPFUI\ORM::beginTransaction());
24+
$transaction = new \PHPFUI\ORM\Transaction();
2525
$table->setWhere(new \PHPFUI\ORM\Condition('customer_id', 9));
2626
$table->delete();
2727
$this->assertEquals(0, $table->count());
2828
$table = new \Tests\App\Table\Customer();
2929
$this->assertEquals(28, $table->count());
30-
$this->assertTrue(\PHPFUI\ORM::rollBack());
30+
$this->assertTrue($transaction->rollBack());
3131
$this->assertEquals(29, $table->count());
3232
}
3333
}

Tests/Unit/InsertTest.php

+59-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,61 @@
44

55
class InsertTest extends \PHPUnit\Framework\TestCase
66
{
7+
public function testStringNullInsert() : void
8+
{
9+
$transaction = new \PHPFUI\ORM\Transaction();
10+
$test = new \Tests\App\Record\StringRecord();
11+
$test->stringRequired = $required = 'required';
12+
$id = $test->insert();
13+
$this->assertGreaterThan(0, $id);
14+
$insertedTest = new \Tests\App\Record\StringRecord($id);
15+
$this->assertNull($insertedTest->stringDefaultNull);
16+
$this->assertEquals($required, $insertedTest->stringRequired);
17+
$this->assertEquals('default', $insertedTest->stringDefaultNullable);
18+
$this->assertEquals('default', $insertedTest->stringDefaultNotNull);
19+
$this->assertTrue($transaction->rollBack());
20+
}
21+
22+
public function testRequiredStringNotSetInsert() : void
23+
{
24+
$this->expectException(\Exception::class);
25+
$transaction = new \PHPFUI\ORM\Transaction();
26+
$test = new \Tests\App\Record\StringRecord();
27+
$id = $test->insert();
28+
$this->assertTrue($transaction->rollBack());
29+
}
30+
31+
public function testDateNullInsert() : void
32+
{
33+
$transaction = new \PHPFUI\ORM\Transaction();
34+
$test = new \Tests\App\Record\DateRecord();
35+
$test->dateRequired = $date = \date('Y-m-d');
36+
$timeStamp = \date('Y-m-d H:i:s');
37+
$id = $test->insert();
38+
$insertedTest = new \Tests\App\Record\DateRecord($id);
39+
$this->assertNull($insertedTest->dateDefaultNull);
40+
$this->assertEquals($date, $insertedTest->dateRequired);
41+
$this->assertEquals('2000-01-02', $insertedTest->dateDefaultNullable);
42+
$this->assertEquals('2000-01-02', $insertedTest->dateDefaultNotNull);
43+
$this->assertGreaterThanOrEqual($timeStamp, $insertedTest->timestampDefaultCurrentNullable);
44+
$this->assertGreaterThanOrEqual($timeStamp, $insertedTest->timestampDefaultCurrentNotNull);
45+
46+
$this->assertTrue($transaction->rollBack());
47+
}
48+
49+
public function testDateRequiredInsert() : void
50+
{
51+
$this->expectException(\Exception::class);
52+
$transaction = new \PHPFUI\ORM\Transaction();
53+
$test = new \Tests\App\Record\DateRecord();
54+
$id = $test->insert();
55+
$insertedTest = new \Tests\App\Record\DateRecord($id);
56+
$this->assertNull($insertedTest->dateDefaultNull);
57+
$this->assertEquals('2000-01-02', $insertedTest->dateDefaultNullable);
58+
$this->assertEquals('2000-01-02', $insertedTest->dateDefaultNotNull);
59+
$this->assertTrue($transaction->rollBack());
60+
}
61+
762
public function testRecordInsert() : void
863
{
964
$customer = new \Tests\App\Record\Customer();
@@ -25,10 +80,10 @@ public function testRecordInsert() : void
2580

2681
$table = new \Tests\App\Table\Customer();
2782
$this->assertEquals(29, $table->count());
28-
$this->assertTrue(\PHPFUI\ORM::beginTransaction());
83+
$transaction = new \PHPFUI\ORM\Transaction();
2984
$this->assertEquals(30, $customer->insert());
3085
$this->assertEquals(30, $table->count());
31-
$this->assertTrue(\PHPFUI\ORM::rollBack());
86+
$this->assertTrue($transaction->rollBack());
3287
$this->assertEquals(29, $table->count());
3388
}
3489

@@ -39,7 +94,7 @@ public function testRelatedInsert() : void
3994
$orderTable = new \Tests\App\Table\Order();
4095
$this->assertEquals(48, $orderTable->count());
4196

42-
$this->assertTrue(\PHPFUI\ORM::beginTransaction());
97+
$transaction = new \PHPFUI\ORM\Transaction();
4398

4499
$customer = new \Tests\App\Record\Customer();
45100
$customer->address = '123 Broadway';
@@ -89,7 +144,7 @@ public function testRelatedInsert() : void
89144
$orderTable->setWhere(new \PHPFUI\ORM\Condition('notes', 'Test Order'));
90145
$foundOrder = $orderTable->getRecordCursor()->current();
91146

92-
$this->assertTrue(\PHPFUI\ORM::rollBack());
147+
$this->assertTrue($transaction->rollBack());
93148
$this->assertEquals(29, $customerTable->count());
94149
$orderTable->setWhere();
95150
$this->assertEquals(48, $orderTable->count());

Tests/Unit/UpdateTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public function testRecordUpdate() : void
1919
$customerTable2->setWhere($condition2);
2020
$this->assertEquals(1, $customerTable2->count());
2121

22-
$this->assertTrue(\PHPFUI\ORM::beginTransaction());
22+
$transaction = new \PHPFUI\ORM\Transaction();
2323
$customer = new \Tests\App\Record\Customer(15);
2424
$this->assertEquals('Helena', $customer->first_name);
2525
$this->assertEquals('Kupkova', $customer->last_name);
@@ -29,7 +29,7 @@ public function testRecordUpdate() : void
2929
$this->assertEquals(1, $customerTable->count());
3030
$this->assertEquals(0, $customerTable2->count());
3131

32-
$this->assertTrue(\PHPFUI\ORM::rollBack());
32+
$this->assertTrue($transaction->rollBack());
3333
$this->assertEquals(0, $customerTable->count());
3434
$this->assertEquals(1, $customerTable2->count());
3535
$customerTable->setWhere();

Tests/bootstrap.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function autoload(string $className) : void
2525
{
2626
if ($fileinfo->isFile() && \str_ends_with($fileinfo->getRealPath(), '.php'))
2727
{
28-
\unlink($fileinfo->getRealPath());
28+
\unlink($fileinfo->getRealPath());
2929
}
3030
}
3131

bootstrap.php

-18
This file was deleted.

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"phpfui/translation": "^1.0"
1717
},
1818
"require-dev": {
19-
"phpunit/phpunit": "^9.0",
19+
"phpunit/phpunit": "^10.0",
2020
"phpfui/phpunit-syntax-coverage": "^1.0",
2121
"roave/security-advisories": "dev-latest",
2222
"friendsofphp/php-cs-fixer": "^3.0",

docs/9. Miscellaneous.md docs/10. Miscellaneous.md

-3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,3 @@ The following namespaces are used in production but contains support classes or
4646
- **PHPFUI\ORM\Tool** used by model generation
4747
- **PHPFUI\ORM\Record** contains example of migration table.
4848
- **PHPFUI\ORM\Table** contains example of migration table.
49-
50-
51-

docs/2. Active Record.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,13 @@ $order->insert();
131131
```
132132
Notice that we did not have to save the customer record. By assigning it to the order record, it was automatically saved to generate the required primary key value. The related record is not saved if it already has been assigned a primary key, it is your responsiblity to save it if you changed an existing record.
133133

134-
### Alternate way to set related records
134+
### Alternate Way To Set Related Records
135135
You can always just assign the id's directly: `$orderDetail->purchase_order_id = $purchase_order->purchase_order_id;`. Saving the OrderDetail record is up to you.
136136

137137
### Other Types Of Related Records
138138
See [Virtual Fields](<https://github.com/phpfui/ORM/blob/main/docs/5. Virtual Fields.md>) for information on how to impliment child or many to many relationships.
139+
140+
### Multi Database Support
141+
Related Records will always return a record from the currently selected database. Care must be taken when using multiple databases that any references to related records are done while the correct database instance is active. Cursors will continue to use the database in effect when they were created.
142+
143+
A future version of this libray may offer better multi database support.

docs/5. Virtual Fields.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class Supplier extends \Tests\Fixtures\Record\Definition\Supplier
8585
### Usage
8686
```php
8787
$product = new \App\Record\Product(4);
88-
$suppliers = $product->suppliers->count();
88+
$suppliers = $product->suppliers;
8989
echo "There are {$suppliers->count()} for product {$product->product_code} - {$product->product_name}:\n";
9090
foreach ($suppliers as $supplier)
9191
{

docs/6. Migrations.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ class Migration_1 extends \PHPFUI\ORM\Migration
5353
```
5454
Migrations must be inherited from [\PHPFUI\ORM\Migration](http://phpfui.com/?n=PHPFUI%5CORM&c=Migration). They must have the following methods defined:
5555
- **description**() : string, returns a human readable description of what the migration does.
56-
- **up**() : bool, performs SQL statements to take the database to the next version.
57-
- **down**() : bool, reverts the up and leaves the database in the prior version state.
56+
- **up**() : bool, performs SQL statements to take the database to the next version. Returns true on success.
57+
- **down**() : bool, reverts the up and leaves the database in the prior version state. Returns true on success.
5858

5959
## Running migrations from code
6060
The [\PHPFUI\ORM\Migrator](http://phpfui.com/?n=PHPFUI%5CORM&c=Migrator) class is used to run migrations automatically. Use the **migrate**() method to go to the latest version, or **migrateTo**() for a specific version. The class handles running migrations in the correct order. The **getStatus**() result should be shown to the user.

docs/7. Validation.md

+13-3
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ foreach ($validationErrors as $field => $fieldErrors)
4141
| day_month_year | Loosely formatted date (D-M-Y) | None |
4242
| domain | Valid domain | None |
4343
| email | Valid email | None |
44-
| enum | MySQL enum value, case insensitive | comma separated list of identifiers<br>**Example:** enum:GET,POST,PUT,DELETE |
44+
| enum | MySQL enum value, case insensitive | comma separated list of identifiers<br>**Example:** enum:Get,Post,Put,Delete |
4545
| enum_exact | MySQL enum value, case sensitive | comma separated list of identifiers<br>**Example:** enum:ssl,tls |
4646
| integer | Whole number, no fractional part | None |
4747
| maxlength | Length must be greater or equal | Optional length, else MySQL limit |
48-
| maxvalue | Value must be greater or equal | number, required |
48+
| maxvalue | Value must be greater or equal | value, required |
4949
| minlength | Must be less than or equal | number, default field size |
50-
| minvalue | Must be less than or equal | number, required |
50+
| minvalue | Must be less than or equal | value, required |
5151
| month_day_year | Loosely formatted date (M-D-Y) | None |
5252
| month_year | Loosely formatted Month Year | None |
5353
| number | Floating point number or whole number | None |
@@ -85,3 +85,13 @@ You may need to do additional checks for a specific record type. A second param
8585

8686
You can also pass an optional method to validate to perform more complex validation. By default, insert, update, and delete are standard methods that are used by the \App\Controller\Record class will use.
8787

88+
## Multi Validator Example
89+
```php
90+
class Order extends \PHPFUI\ORM\Validator
91+
{
92+
/** @var array<string, string[]> */
93+
public static array $validators = [
94+
'order_date' => ['required', 'maxlength', 'datetime', 'minvalue:2000-01-01', 'maxvalue:2099-12-31'],
95+
];
96+
}
97+
```

docs/9. Transactions.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# PHPFUI\ORM Transactions
2+
While PHPFUI\ORM supports the traditional beginTransaction(), commit() and rollBack() on the PDO object, it is recommended you use the \PHPFUI\ORM\Transaction class.
3+
4+
```php
5+
$transaction = new \PHPFUI\ORM\Transaction();
6+
// do some stuff
7+
if ($allGood)
8+
{
9+
$transaction->commit();
10+
}
11+
else
12+
{
13+
$transaction->rollBack();
14+
}
15+
```
16+
The above creates a transaction on the current database. Commit and rollback will also be called on the correct database even if you are working on another database at the time.
17+
18+
The main advantage of a Transaction object, it that will will rollback any changes on a thrown exception assuming the transaction object is properly scoped.
19+
20+

northwind/northwind-default.sql

+20
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,26 @@ CREATE TABLE IF NOT EXISTS `setting` (
496496
`setting_id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
497497
`setting_data` VARCHAR(255) NULL DEFAULT NULL);
498498

499+
drop table if exists stringRecord;
500+
CREATE TABLE stringRecord (
501+
stringRecordId int NOT NULL AUTO_INCREMENT,
502+
stringRequired varchar(100) not null,
503+
stringDefaultNull varchar(100) DEFAULT NULL,
504+
stringDefaultNullable varchar(100) default 'default',
505+
stringDefaultNotNull varchar(100) not null default 'default',
506+
primary key(stringRecordId));
507+
508+
drop table if exists dateRecord;
509+
create table dateRecord (
510+
dateRecordId int not null auto_increment,
511+
dateRequired date not null,
512+
dateDefaultNull date DEFAULT NULL,
513+
dateDefaultNullable date default '2000-01-02',
514+
dateDefaultNotNull date not null default '2000-01-02',
515+
timestampDefaultCurrentNullable timestamp DEFAULT CURRENT_TIMESTAMP,
516+
timestampDefaultCurrentNotNull timestamp not null default CURRENT_TIMESTAMP,
517+
PRIMARY KEY (dateRecordId));
518+
499519
SET SQL_MODE=@OLD_SQL_MODE;
500520
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
501521
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

northwind/northwind-sqlite.sql

+17
Original file line numberDiff line numberDiff line change
@@ -488,3 +488,20 @@ CREATE TABLE IF NOT EXISTS `setting` (
488488
`setting_id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
489489
`setting_data` VARCHAR(255) NULL DEFAULT NULL);
490490

491+
drop table if exists stringRecord;
492+
CREATE TABLE stringRecord (
493+
stringRecordId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
494+
stringRequired varchar(100) not null,
495+
stringDefaultNull varchar(100) DEFAULT NULL,
496+
stringDefaultNullable varchar(100) default 'default',
497+
stringDefaultNotNull varchar(100) not null default 'default');
498+
499+
drop table if exists dateRecord;
500+
create table dateRecord (
501+
dateRecordId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
502+
dateRequired date not null,
503+
dateDefaultNull date DEFAULT NULL,
504+
dateDefaultNullable date default '2000-01-02',
505+
dateDefaultNotNull date not null default '2000-01-02',
506+
timestampDefaultCurrentNullable timestamp DEFAULT CURRENT_TIMESTAMP,
507+
timestampDefaultCurrentNotNull timestamp not null default CURRENT_TIMESTAMP);

northwind/northwind.db

8 KB
Binary file not shown.

0 commit comments

Comments
 (0)