Skip to content

Commit 2284667

Browse files
committed
Added automigration and moved to php 8.1
1 parent a174955 commit 2284667

40 files changed

Lines changed: 1577 additions & 173 deletions

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
/testinglocal
1+
/testinglocal
2+
.idea
3+
*.iml

README.md

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
# Ulole-ORM `2.0`
1+
# Ulole-ORM `3.0`
2+
23
## Getting started
34
UloleORM is an Object Relation Mapper written in PHP.
45
#### Uppm
@@ -13,14 +14,27 @@ composer require interaapps/uloleorm
1314
```php
1415
<?php
1516
use de\interaapps\ulole\orm\ORMModel;
17+
use de\interaapps\ulole\orm\attributes\Column;
18+
use de\interaapps\ulole\orm\attributes\Table;
1619

20+
#[Table("users")]
1721
class User {
1822
use ORMModel;
1923

20-
public $id;
21-
public $name;
22-
public $password;
23-
public $description;
24+
#[Column]
25+
public int $id;
26+
27+
#[Column]
28+
public ?string $name;
29+
30+
#[Column(name: 'mail')]
31+
public ?string $eMail;
32+
33+
#[Column]
34+
public ?string $password;
35+
36+
#[Column]
37+
public ?string $description;
2438
}
2539
```
2640
### example.php
@@ -35,7 +49,10 @@ UloleORM::database("main", new Database(
3549
'mysql' /* DRIVER: default mysql (Every PDO Driver usable. ) */
3650
));
3751

38-
UloleORM::register(/*table-name*/ "user", /*Model class*/ User::class);
52+
UloleORM::register(User::class);
53+
54+
// Auto migrates all tables (You might not do this every time the user opens the page. Move it into a cli-command or something like this)
55+
UloleORM::autoMigrate();
3956

4057
// Inserting into table
4158
$user = new User;
@@ -149,6 +166,11 @@ User::table()->each(function(User $entry){
149166
->fromFolder("resources/migrations")
150167
->down(/*default val: 1*/);
151168
```
169+
## Auto-Migrate
170+
```php
171+
// Automatically migrates all columns by its class structure and attributes
172+
UloleORM::autoMigrate();
173+
```
152174

153175
#### resources/migrations/migration_22_0_11_create_users.php
154176
```php

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "interaapps/uloleorm",
3-
"version": "2.0.1",
3+
"version": "3.0.0",
44
"type": "library",
55
"authors": [
66
{
@@ -9,7 +9,8 @@
99
}
1010
],
1111
"require": {
12-
"php": ">=7.3.0"
12+
"php": ">=8.1",
13+
"ext-pdo": "*"
1314
},
1415
"autoload": {
1516
"psr-4": {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace de\interaapps\ulole\orm;
4+
5+
use de\interaapps\ulole\orm\attributes\Column;
6+
use ReflectionIntersectionType;
7+
use ReflectionNamedType;
8+
use ReflectionProperty;
9+
use ReflectionType;
10+
use ReflectionUnionType;
11+
12+
class ColumnInformation
13+
{
14+
private ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType|null $type;
15+
16+
public function __construct(
17+
private string $fieldName,
18+
private Column $columnAttribute,
19+
private ReflectionProperty $property
20+
)
21+
{
22+
$this->type = $property->getType();
23+
}
24+
25+
26+
public function getFieldName(): string {
27+
return $this->fieldName;
28+
}
29+
30+
public function getColumnAttribute(): Column {
31+
return $this->columnAttribute;
32+
}
33+
34+
public function getProperty(): ReflectionProperty {
35+
return $this->property;
36+
}
37+
38+
public function getType(): ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType|null {
39+
return $this->type;
40+
}
41+
}

de/interaapps/ulole/orm/Database.php

Lines changed: 105 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,54 @@
22
namespace de\interaapps\ulole\orm;
33

44
use de\interaapps\ulole\orm\migration\Blueprint;
5+
use PDO;
6+
use PDOStatement;
57

68
class Database {
7-
private $connection;
9+
private PDO $connection;
810

11+
private const PHP_SQL_TYPES = [
12+
"int" => "INTEGER",
13+
"float" => "FLOAT",
14+
"string" => "TEXT",
15+
"bool" => "TINYINT"
16+
];
17+
18+
/**
19+
* @param $username
20+
* @param $password
21+
* @param $database
22+
* @param $host
23+
* @param $port
24+
* @param $driver
25+
* @throws \PDOException
26+
*/
927
public function __construct($username, $password=false, $database=false,$host='localhost',$port=3306, $driver="mysql") {
1028
if ($driver=="sqlite")
11-
$this->connection = new \PDO($driver.':'.$database);
29+
$this->connection = new PDO($driver.':'.$database);
1230
else
13-
$this->connection = new \PDO($driver.':host='.$host.';dbname='.$database, $username, $password);
31+
$this->connection = new PDO($driver.':host='.$host.';dbname='.$database, $username, $password);
1432
}
1533

16-
public function getConnection(){
34+
public function getConnection() : PDO {
1735
return $this->connection;
1836
}
1937

20-
public function create($name, $callable, $ifNotExists = false){
38+
public function query($sql) : PDOStatement|bool {
39+
return $this->connection->query($sql);
40+
}
41+
42+
public function create($name, $callable, $ifNotExists = false) : PDOStatement|bool {
2143
$blueprint = new Blueprint();
2244
$callable($blueprint);
2345
$sql = "CREATE TABLE ".($ifNotExists ? "IF NOT EXISTS " : "")."`".$name."` (\n";
2446
$sql .= implode(",\n", $blueprint->getQueries(true));
2547
$sql .= "\n) ENGINE = InnoDB;";
2648

27-
return $this->connection->query($sql);
49+
return $this->query($sql);
2850
}
2951

30-
public function edit($name, $callable){
52+
public function edit($name, $callable) : PDOStatement|bool {
3153
$statement = $this->connection->query("SHOW COLUMNS FROM ".$name.";");
3254
$existingColumns = [];
3355
foreach ($statement->fetchAll(\PDO::FETCH_NUM) as $row) {
@@ -51,12 +73,85 @@ public function edit($name, $callable){
5173
}
5274
$sql .= ";";
5375

54-
return $this->connection->query($sql);
76+
return $this->query($sql);
5577
}
5678

5779

58-
public function drop($name){
59-
return $this->connection->query("DROP TABLE `".$name."`;");
80+
public function drop($name) : PDOStatement|bool {
81+
return $this->query("DROP TABLE `".$name."`;");
82+
}
83+
84+
public function autoMigrate() {
85+
$tables = [];
86+
foreach ($this->query("SHOW TABLES;")->fetchAll() as $r) {
87+
$tables[] = $r[0];
88+
}
89+
90+
foreach (UloleORM::getModelInformationList() as $modelInformation) {
91+
$fields = $modelInformation->getFields();
92+
$columns = array_map(function ($field) use ($modelInformation) {
93+
$type = $field->getColumnAttribute()->sqlType;
94+
if ($type == null) {
95+
if (isset(self::PHP_SQL_TYPES[$field->getType()->getName()]))
96+
$type = self::PHP_SQL_TYPES[$field->getType()->getName()];
97+
}
98+
99+
if ($field->getColumnAttribute()->size != null)
100+
$type .= "(".$field->getColumnAttribute()->size.")";
101+
102+
$isIdentifier = $modelInformation->getIdentifier() == $field->getFieldName();
103+
104+
return [
105+
"field" => $field->getFieldName(),
106+
"type" => $type,
107+
"hasIndex" => $field->getColumnAttribute()->index,
108+
"identifier" => $isIdentifier,
109+
"query" => "`" . $field->getFieldName() . "` "
110+
. $type
111+
. ($field->getType()->allowsNull() ? '' : ' NOT NULL')
112+
. ($type == 'INTEGER' && $isIdentifier ? ' AUTO_INCREMENT' : '')
113+
];
114+
}, $fields);
115+
116+
$indexes = array_filter($columns, fn($c) => $c["hasIndex"]);
117+
118+
if (in_array($modelInformation->getName(), $tables)) {
119+
$existingFields = array_map(fn($f) => $f[0], $this->query('SHOW COLUMNS FROM '.$modelInformation->getName().';')->fetchAll());
120+
$existingIndexes = array_map(fn($f) => $f[4], $this->query('SHOW INDEXES FROM '.$modelInformation->getName().';')->fetchAll());
121+
122+
$q = "ALTER TABLE `" . $modelInformation->getName() . "` ";
123+
$changes = [];
124+
foreach ($columns as $column) {
125+
if (in_array($column['field'], $existingFields)) {
126+
$changes[] = "MODIFY COLUMN " . $column["query"];
127+
} else {
128+
$changes[] = "ADD " . $column["query"];
129+
}
130+
}
131+
foreach ($indexes as $index) {
132+
$index = $index["field"];
133+
if (!in_array($index, $existingIndexes))
134+
$changes[] = 'ADD INDEX (`'.$index.'`);';
135+
}
136+
137+
$q .= implode(", ", $changes) . ';';
138+
$this->query($q);
139+
} else {
140+
$q = "CREATE TABLE " . $modelInformation->getName() . " (" .
141+
implode(', ', array_map(fn($c) =>
142+
$c['query']
143+
.($c['identifier'] ? ' PRIMARY KEY' : ''
144+
), $columns)
145+
);
146+
147+
if (count($indexes) > 0) {
148+
$q .= ", INDEX (" . implode(", ", array_map(fn($c) => $c["field"], $indexes)) . ")";
149+
}
150+
151+
$q .= ");";
152+
$this->query($q);
153+
}
154+
}
60155
}
61156

62157
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
namespace de\interaapps\ulole\orm;
4+
5+
use de\interaapps\ulole\orm\attributes\Column;
6+
use de\interaapps\ulole\orm\attributes\Table;
7+
use ReflectionClass;
8+
use ReflectionException;
9+
10+
/**
11+
* @template T
12+
*/
13+
class ModelInformation {
14+
private string $identifier = "id";
15+
/**
16+
* @var ColumnInformation[]
17+
*/
18+
private array $fields;
19+
private ?string $name;
20+
21+
/**
22+
* @throws ReflectionException
23+
*/
24+
public function __construct(
25+
private string $class
26+
)
27+
{
28+
$reflection = new ReflectionClass($this->class);
29+
30+
$tableAttributes = $reflection->getAttributes(Table::class);
31+
32+
if (count($tableAttributes) > 0) {
33+
$tableAttribute = $tableAttributes[0]->newInstance();
34+
$this->name = $tableAttribute->value;
35+
} else {
36+
$this->name = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $reflection->getShortName()));
37+
if (!str_ends_with($this->name, "s"))
38+
$this->name .= "s";
39+
}
40+
41+
foreach ($reflection->getProperties() as $property) {
42+
if (!$property->isStatic()) {
43+
$columnAttributes = $property->getAttributes(Column::class);
44+
if (count($columnAttributes) > 0) {
45+
$columnAttribute = $columnAttributes[0]->newInstance();
46+
$this->fields[$property->getName()] = new ColumnInformation($columnAttribute->name ?? $property->getName(), $columnAttribute, $property);
47+
}
48+
}
49+
}
50+
}
51+
52+
public function getName(): string {
53+
return $this->name;
54+
}
55+
56+
public function getClass(): string {
57+
return $this->class;
58+
}
59+
60+
/**
61+
* @return ColumnInformation[]
62+
*/
63+
public function getFields(): array {
64+
return $this->fields;
65+
}
66+
67+
public function setIdentifier(string $identifier): void {
68+
$this->identifier = $identifier;
69+
}
70+
71+
public function getIdentifier(): string {
72+
return $this->identifier;
73+
}
74+
75+
public function setName(string $name): ModelInformation {
76+
$this->name = $name;
77+
return $this;
78+
}
79+
80+
public function getFieldName(string $field) : string {
81+
return $this->getColumnInformation($field)->getFieldName();
82+
}
83+
84+
public function getColumnInformation(string $name) : ColumnInformation {
85+
return $this->fields[$name];
86+
}
87+
88+
public function getFieldValue($obj, string $field) : string {
89+
90+
91+
return $obj->{$this->getFieldName($field)};
92+
}
93+
94+
public function getIdentifierValue($obj) : string {
95+
return $obj->{$this->getIdentifier()};
96+
}
97+
}

0 commit comments

Comments
 (0)