Skip to content

Commit f803d35

Browse files
committed
MorphMany (like Eloquent) support
1 parent 1416e0a commit f803d35

File tree

7 files changed

+162
-0
lines changed

7 files changed

+162
-0
lines changed

Tests/Fixtures/Record/Employee.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Tests\Fixtures\Record;
4+
5+
class Employee extends \Tests\App\Record\Definition\Employee
6+
{
7+
protected static array $virtualFields = [
8+
'photos' => [\PHPFUI\ORM\MorphMany::class, \Tests\App\Table\Image::class, 'imageable', ],
9+
];
10+
}

Tests/Fixtures/Record/Product.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ class Product extends \Tests\Fixtures\Record\Definition\Product
66
{
77
protected static array $virtualFields = [
88
'suppliers' => [\PHPFUI\ORM\ManyToMany::class, \Tests\App\Table\ProductSupplier::class, \Tests\App\Table\Supplier::class],
9+
'photos' => [\PHPFUI\ORM\MorphMany::class, \Tests\App\Table\Image::class, 'imageable', ],
910
];
1011
}

Tests/Unit/MorphManyTest.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
namespace Tests\Unit;
4+
5+
class MorphManyTest extends \PHPUnit\Framework\TestCase
6+
{
7+
public function testMorphMany() : void
8+
{
9+
$transaction = new \PHPFUI\ORM\Transaction();
10+
$product = new \Tests\Fixtures\Record\Product(43);
11+
$this->assertTrue($product->loaded());
12+
$image = new \Tests\App\Record\Image();
13+
$image->path = 'product_1.jpg';
14+
$product->photos = $image;
15+
$this->assertCount(1, $product->photos);
16+
$this->assertTrue($image->loaded());
17+
$image = new \Tests\App\Record\Image();
18+
$image->path = 'product_2.jpg';
19+
$product->photos = $image;
20+
$this->assertCount(2, $product->photos);
21+
$this->assertTrue($image->loaded());
22+
23+
foreach ($product->photos as $image)
24+
{
25+
$this->assertStringContainsString('product', $image->path);
26+
}
27+
28+
$employee = new \Tests\Fixtures\Record\Employee(1);
29+
$this->assertTrue($employee->loaded());
30+
$image = new \Tests\App\Record\Image();
31+
$image->path = 'profile_1.jpg';
32+
$employee->photos = $image;
33+
$this->assertCount(1, $employee->photos);
34+
$this->assertTrue($image->loaded());
35+
$image = new \Tests\App\Record\Image();
36+
$image->path = 'profile_2.jpg';
37+
$employee->photos = $image;
38+
$this->assertCount(2, $employee->photos);
39+
$this->assertTrue($image->loaded());
40+
41+
foreach ($employee->photos as $image)
42+
{
43+
$this->assertStringContainsString('profile', $image->path);
44+
}
45+
46+
$imageTable = new \Tests\App\Table\Image();
47+
$this->assertCount(4, $imageTable);
48+
49+
foreach ($imageTable as $image)
50+
{
51+
$this->assertStringContainsString('.jpg', $image->path);
52+
}
53+
54+
$product->photos->current()->delete();
55+
$employee->photos->current()->delete();
56+
$this->assertCount(2, $imageTable);
57+
58+
$this->assertTrue($transaction->rollBack());
59+
}
60+
}

northwind/northwind-default.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,14 @@ create table dateRecord (
519519
drop table if exists migration;
520520
create table migration (migrationId int NOT NULL primary key, ran TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
521521

522+
DROP TABLE IF EXISTS image;
523+
CREATE TABLE `image` (
524+
`imageId` INTEGER NOT NULL AUTO_INCREMENT,
525+
`imageableId` INTEGER,
526+
`imageable_type` VARCHAR(128),
527+
`path` VARCHAR(128) NOT NULL,
528+
PRIMARY KEY (imageId));
529+
522530
SET SQL_MODE=@OLD_SQL_MODE;
523531
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
524532
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

northwind/northwind-sqlite.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,14 @@ create table dateRecord (
506506
timestampDefaultCurrentNullable timestamp DEFAULT CURRENT_TIMESTAMP,
507507
timestampDefaultCurrentNotNull timestamp not null default CURRENT_TIMESTAMP);
508508

509+
510+
DROP TABLE IF EXISTS image;
511+
CREATE TABLE `image` (
512+
`image_id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
513+
`imageable_id` INTEGER,
514+
`imageable_type` VARCHAR(128),
515+
`path` VARCHAR(128) NOT NULL);
516+
509517
drop table if exists migration;
510518
create table migration (migrationId int NOT NULL primary key, ran TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
511519

northwind/northwind.db

4 KB
Binary file not shown.

src/PHPFUI/ORM/MorphMany.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace PHPFUI\ORM;
4+
5+
/**
6+
* A MorphMany class to mimic Eloquent MorphMany.
7+
*
8+
* Requirements:
9+
*
10+
* The morphing records needs two fields: an integer related key and a string field type long enough to hold the table name of the relationship. They should both be prefixed with the morph field prefix passed into the class. The related key field should follow the same id naming conventions as the rest of the database.
11+
*
12+
* In the **Record** class definition, you need to define a virtual field with the name of the MorphMany relationship. The key of the virtual field is the member name and the value is an array.
13+
*
14+
* The values in the array should be **\PHPFUI\ORM\MorphMany::class**, followed by the morphing table class name, then morph field prefix. Sort field and sort order are optional fields.
15+
*
16+
* Example:
17+
* ```php
18+
* protected static array $virtualFields = [
19+
* 'images' => [\PHPFUI\ORM\MorphToMany::class, \Tests\App\Table\Image::class, 'imageable', ],
20+
* ];
21+
* ```
22+
*/
23+
class MorphMany extends \PHPFUI\ORM\VirtualField
24+
{
25+
/**
26+
* @param array<string> $parameters morphing table class name, morph field prefix, optional sort column, optional sort order.
27+
*/
28+
public function getValue(array $parameters) : \PHPFUI\ORM\RecordCursor
29+
{
30+
$morphTableClass = \array_shift($parameters);
31+
$morphTable = new $morphTableClass();
32+
$morphFieldPrefix = \array_shift($parameters);
33+
$condition = new \PHPFUI\ORM\Condition($morphFieldPrefix . '_type', $this->currentRecord->getTableName());
34+
$primaryKey = $this->currentRecord->getPrimaryKeys()[0];
35+
$condition->and($morphFieldPrefix . \PHPFUI\ORM::$idSuffix, $this->currentRecord->{$primaryKey});
36+
$morphTable->setWhere($condition);
37+
38+
$orderBy = \array_shift($parameters);
39+
$sort = \array_shift($parameters) ?? 'asc';
40+
41+
if ($orderBy)
42+
{
43+
$morphTable->addOrderBy($orderBy, $sort);
44+
}
45+
46+
return $morphTable->getRecordCursor();
47+
}
48+
49+
/**
50+
* @param mixed $value to add as morph relation for the current record
51+
* @param array<string> $parameters morphing table class name, morph field prefix, optional sort column, optional sort order
52+
*/
53+
public function setValue(mixed $value, array $parameters) : void
54+
{
55+
$morphTableClass = \array_shift($parameters);
56+
$morphTable = new $morphTableClass();
57+
$morphFieldPrefix = \array_shift($parameters);
58+
$primaryKey = $this->currentRecord->getPrimaryKeys()[0];
59+
60+
$morphTypeField = $morphFieldPrefix . '_type';
61+
$morphIdField = $morphFieldPrefix . \PHPFUI\ORM::$idSuffix;
62+
63+
$value->{$morphTypeField} = $this->currentRecord->getTableName();
64+
$primaryKeyValue = $this->currentRecord->{$primaryKey};
65+
66+
if (! $primaryKeyValue)
67+
{
68+
$this->currentRecord->insert();
69+
$primaryKeyValue = $this->currentRecord->{$primaryKey};
70+
}
71+
$value->{$morphIdField} = $primaryKeyValue;
72+
73+
$value->insert();
74+
}
75+
}

0 commit comments

Comments
 (0)