Skip to content

Commit

Permalink
wip: write up work so far
Browse files Browse the repository at this point in the history
  • Loading branch information
g105b committed Oct 1, 2023
1 parent e0aeed9 commit 84f7e6f
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 5 deletions.
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
This repository is currently in a prototype stage. I'm planning on building it out properly, but it may never get completed, or I might change it completely without notice.

Please don't use on anything real until a stable release is made!

## Notes

Basic table creation is possible like this:

```php
$generator = new SchemaGenerator();
$studentSchemaTable = $generator->generate(Student::class);
```

Then the `$studentSchemaTable` can be passed to the actual underlying database for it to execute as SQL.

For example:

```php
$db->executeSQL($studentSchemaTable);
```

Here's an example of what the `Student` class looks like, and how the `SchemaGenerator` stringifies it as SQLite:

```php
readonly class Student {
public function __construct(
public int $id,
public string $name,
public DateTime $dob,
) {}
}
```

```sql
create table `Student` (
`id` int not null primary key,
`name` text not null,
`dob` int not null
)
```

Some questions I need to answer before I go any further:

- How should the `DateTime` class be cast back and forth between different database engines? MySQL has a `datetime` type, but SQLite has to use a timestamp.
- How should the different constraints be handled? I like the idea of adding an attribute to describe the primary key: `#[PrimaryKey("id", PrimaryKey::AUTOINCREMENT)]`
- Straight-up foreign keys should be easy to implement - use a class as a public property.
- A common OOP technique is to have an array/iterable of objects. For example, the `Lesson` class can have an `array<Student>` or a custom `StudentCollection` class.
- This means a `StudentCollection` must be a differently derived class than `Student`, as it represents a junction table.

One big question I have yet to prototype:

- Cyclic dependencies are OK and sometimes really useful, especially in OOP land, but a recursive SQL query would be really inefficient on big data structures.
- The foreign key should not be loaded until it's used in code (lazy load), but this is going to require some clever programming for a good developer experience.

I think the way this should work is foreign keys are never done using joins - instead, separate queries should always be used. That way, the query that loads the referenced table will not need to be executed until the developer requests that field.

This could be achieved by the Orm generating an anonymous class that extends the referenced class, but takes on a trait to allow `__get` to execute the query... something like that, but I expect weird reflection will be required to make this transparent to the developer.
57 changes: 52 additions & 5 deletions example/01-create-table.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php
use Gt\Orm\Attribute\PrimaryKey;
use Gt\Orm\Migration\Query\SchemaQuerySQLite;
use Gt\Orm\Migration\SchemaGenerator;
use Gt\Orm\Migration\SchemaTable;
Expand All @@ -9,6 +10,7 @@
// - The Student class represents an individual student
// - The Lesson class represents a lesson, which is assigned an array of Students.

#[PrimaryKey("id", PrimaryKey::AUTOINCREMENT)]
readonly class Student {
public function __construct(
public int $id,
Expand All @@ -17,22 +19,67 @@ public function __construct(
) {}
}

#[PrimaryKey("id", PrimaryKey::AUTOINCREMENT)]
readonly class Lesson {
/** @param array<Student> $students */
public function __construct(
public int $id,
public string $name,
public array $students,
) {}
// public StudentCollection $students,
) {
$firstStudent = $this->students->offsetGet(123);
$id = $firstStudent->id;

foreach($this->students as $student) {
$id = $student->id;
}
}
}

///** @extends Collection<int, Student> */
//class StudentCollection extends Collection {}
//
///**
// * @template TKey of array-key
// * @template TValue
// * @implements ArrayAccess<TKey, TValue>
// * @implements Iterator<TKey, TValue>
// * @implements Generator<TKey, TValue>
// */
//class Collection implements ArrayAccess, Iterator {
// /**
// * @phpstan-param array<TKey, TValue> $items
// * @param array<mixed, mixed> $items
// */
// public function __construct(
// /**
// * @phpstan-var array<TKey, TValue>
// * @var array
// */
// private array $items
// ) {
// }
//
// /**
// * @phpstan-param TKey $offset
// * @return TValue
// */
// public function offsetGet(mixed $offset):mixed {
// return $this->items[$offset];
// }
//
// /** @return TValue */
// public function current():mixed {
// // TODO: Implement current() method.
// }
//}

// Let's create the schema as a SQLite database.
$generator = new SchemaGenerator();
$studentSchemaTable = $generator->generate(Student::class);
$lessonSchemaTable = $generator->generate(Lesson::class);
//$lessonSchemaTable = $generator->generate(Lesson::class);

$createTableSql = implode(";\n", [
(new SchemaQuerySQLite($studentSchemaTable))->generateSql(),
(new SchemaQuerySQLite($lessonSchemaTable))->generateSql(),
// (new SchemaQuerySQLite($lessonSchemaTable))->generateSql(),
]);
echo $createTableSql;
18 changes: 18 additions & 0 deletions src/Attribute/PrimaryKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
namespace Gt\Orm\Attribute;

use Attribute;

#[Attribute]
readonly class PrimaryKey {
const AUTOINCREMENT = "autoincrement";

public bool $autoIncrement;

public function __construct(
public string $fieldName,
string...$modifiers
) {
$this->autoIncrement = in_array(self::AUTOINCREMENT, $modifiers);
}
}

0 comments on commit 84f7e6f

Please sign in to comment.