Skip to content

Commit d8d49d5

Browse files
authored
Complexity Refactor (#29)
* Refactor SQL create table generator to lower complexity * Getters converted to Classes Getter classes are registered with the Factory and become methods you can run on the Factory. The existing Getters trait for ActiveRecord models scans Factory for Getters and attached them to itself as well providing $className::$methodName() as static calls while using a Factory backend. * Broke up Records controller and Media controller into individual classes for each Endpoint * Lower complexity and update the readme. * Type hints and null hints support instead of notnull attribute * PHPDoc pass
1 parent bf95929 commit d8d49d5

58 files changed

Lines changed: 2871 additions & 2019 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

readme.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ Divergence is a PHP framework designed for rapid development and modern practice
77

88
## [Documentation](https://github.com/Divergence/docs#divergence-framework-documentation)
99
## [Getting Started](https://github.com/Divergence/docs/blob/release/gettingstarted.md#getting-started)
10-
## [V3 Architecture](docs/v3-architecture.md)
1110

1211
## Minimal Model
1312

@@ -33,7 +32,7 @@ class Article extends Model
3332

3433

3534
## Purpose
36-
This collection of classes contains my favorite building blocks for developing websites with PHP and they have an impressive track record with hundreds of currently active websites using one version or another of the classes in this framework. While they were originally written years ago they are all PSR compatible and support modern practices out of the box.
35+
Divergence is a full-featured ActiveRecord framework built on a reflection-driven DTO-style backend. It is designed for performance, and it backs that up with benchmarks. It gives developers a fast procedural-global path for getting real work done, while its internal abstractions stay disciplined and modern. Divergence follows PSR-4, PSR-7, and PSR-15 wherever doing so strengthens the framework instead of turning it into ceremony.
3736

3837
Unit testing the code base and providing code coverage is a primary goal of this project.
3938

@@ -44,7 +43,7 @@ Unit testing the code base and providing code coverage is a primary goal of this
4443
* Declare relationships with static arrays or PHP 8 attributes (`#[Relation(...)]`).
4544
* Built in support for relationships and object versioning.
4645
* Speed up prototyping and automate new deployments by automatically creating tables based on your models when none are found.
47-
* Built in support for MySQL and SQLite.
46+
* Built in support for MySQL, PostgreSQL, and SQLite.
4847

4948
* Routing
5049
* Simpler, faster, tree based routing system.
@@ -55,6 +54,7 @@ Unit testing the code base and providing code coverage is a primary goal of this
5554
* Pre-made REST API controllers allow you to build APIs rapidly.
5655
* 100% Unit test coverage for filters, sorters, and conditions.
5756
* Build HTTP APIs in minutes by extending `RecordsRequestHandler` and setting the one config variable: the name of your model class.
57+
* `RecordsRequestHandler` and `MediaRequestHandler` route response-producing actions through focused endpoint classes instead of one large handler method pile.
5858
* Use a pre-made security trait with RecordsRequestHandler or extend it and write in your own permissions.
5959
* Standard permissions interface allows reuse of permission traits from one model to another.
6060

@@ -85,8 +85,14 @@ composer test
8585
# MySQL suite only
8686
composer test:mysql
8787

88+
# PostgreSQL suite only
89+
composer test:pgsql
90+
8891
# SQLite in-memory suite only
8992
composer test:sqlite
93+
94+
# Run merged coverage across MySQL, PostgreSQL, and SQLite
95+
composer test:coverage
9096
```
9197

9298
### Contributing To Divergence
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
/**
3+
* This file is part of the Divergence package.
4+
*
5+
* (c) Henry Paradiz <henry.paradiz@gmail.com>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
11+
namespace Divergence\Controllers\Media;
12+
13+
use Psr\Http\Message\ResponseInterface;
14+
15+
abstract class AbstractMediaEndpoint
16+
{
17+
abstract public function handle(...$arguments): ResponseInterface;
18+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace Divergence\Controllers\Media\Endpoints;
4+
5+
use Divergence\Controllers\Media\AbstractMediaEndpoint;
6+
use Divergence\Controllers\MediaRequestHandler;
7+
use Divergence\Models\Tag;
8+
use Psr\Http\Message\ResponseInterface;
9+
10+
class Browse extends AbstractMediaEndpoint
11+
{
12+
protected MediaRequestHandler $handler;
13+
14+
public function __construct(MediaRequestHandler $handler)
15+
{
16+
$this->handler = $handler;
17+
}
18+
19+
public function handle(...$arguments): ResponseInterface
20+
{
21+
[$options, $conditions, $responseID, $responseData] = array_pad($arguments, 4, null);
22+
$conditions ??= [];
23+
$responseData ??= [];
24+
25+
if (!empty($_REQUEST['tag'])) {
26+
if (!$Tag = Tag::getByHandle($_REQUEST['tag'])) {
27+
return $this->handler->throwNotFoundError();
28+
}
29+
30+
$conditions[] = 'ID IN (SELECT ContextID FROM tag_items WHERE TagID = '.$Tag->ID.' AND ContextClass = "Product")';
31+
}
32+
33+
if (!empty($_REQUEST['ContextClass'])) {
34+
$conditions['ContextClass'] = $_REQUEST['ContextClass'];
35+
}
36+
37+
if (!empty($_REQUEST['ContextID']) && is_numeric($_REQUEST['ContextID'])) {
38+
$conditions['ContextID'] = $_REQUEST['ContextID'];
39+
}
40+
41+
return $this->parentBrowse($options, $conditions, $responseID, $responseData);
42+
}
43+
44+
protected function parentBrowse($options, $conditions, $responseID, $responseData): ResponseInterface
45+
{
46+
return $this->handler->__call('handleBrowseRequest', [$options, $conditions, $responseID, $responseData]);
47+
}
48+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Divergence\Controllers\Media\Endpoints;
4+
5+
use Divergence\Controllers\Media\AbstractMediaEndpoint;
6+
use Divergence\Controllers\MediaRequestHandler;
7+
use Divergence\Models\Media\Media;
8+
use Exception;
9+
use Psr\Http\Message\ResponseInterface;
10+
11+
class Caption extends AbstractMediaEndpoint
12+
{
13+
protected MediaRequestHandler $handler;
14+
15+
public function __construct(MediaRequestHandler $handler)
16+
{
17+
$this->handler = $handler;
18+
}
19+
20+
public function handle(...$arguments): ResponseInterface
21+
{
22+
[$mediaId] = $arguments;
23+
24+
$GLOBALS['Session']->requireAccountLevel('Staff');
25+
26+
if (empty($mediaId) || !is_numeric($mediaId)) {
27+
return $this->handler->throwNotFoundError();
28+
}
29+
30+
try {
31+
$Media = Media::getById($mediaId);
32+
} catch (Exception $e) {
33+
return $this->handler->throwUnauthorizedError();
34+
}
35+
36+
if (!$Media) {
37+
return $this->handler->throwNotFoundError();
38+
}
39+
40+
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
41+
$Media->Caption = $_REQUEST['Caption'];
42+
$Media->save();
43+
44+
return $this->handler->respond('mediaCaptioned', [
45+
'success' => true,
46+
'data' => $Media,
47+
]);
48+
}
49+
50+
return $this->handler->respond('mediaCaption', [
51+
'data' => $Media,
52+
]);
53+
}
54+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
namespace Divergence\Controllers\Media\Endpoints;
4+
5+
use Divergence\Controllers\Media\AbstractMediaEndpoint;
6+
use Divergence\Controllers\MediaRequestHandler;
7+
use Psr\Http\Message\ResponseInterface;
8+
9+
class Delete extends AbstractMediaEndpoint
10+
{
11+
protected MediaRequestHandler $handler;
12+
13+
public function __construct(MediaRequestHandler $handler)
14+
{
15+
$this->handler = $handler;
16+
}
17+
18+
public function handle(...$arguments): ResponseInterface
19+
{
20+
$GLOBALS['Session']->requireAccountLevel('Staff');
21+
22+
$mediaIds = [];
23+
24+
if ($mediaID = $this->handler->peekPath()) {
25+
$mediaIds = [$mediaID];
26+
} elseif (!empty($_REQUEST['mediaID'])) {
27+
$mediaIds = [$_REQUEST['mediaID']];
28+
} elseif (isset($_REQUEST['media']) && is_array($_REQUEST['media'])) {
29+
$mediaIds = $_REQUEST['media'];
30+
}
31+
32+
$deleted = [];
33+
34+
foreach ($mediaIds as $mediaID) {
35+
if (!is_numeric($mediaID)) {
36+
continue;
37+
}
38+
39+
$Media = \Divergence\Models\Media\Media::getByID($mediaID);
40+
41+
if (!$Media) {
42+
return $this->handler->throwNotFoundError();
43+
}
44+
45+
if ($Media->destroy()) {
46+
$deleted[] = $Media;
47+
}
48+
}
49+
50+
return $this->handler->respond('mediaDeleted', [
51+
'success' => true,
52+
'data' => $deleted,
53+
]);
54+
}
55+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Divergence\Controllers\Media\Endpoints;
4+
5+
use Divergence\Controllers\Media\AbstractMediaEndpoint;
6+
use Divergence\Controllers\MediaRequestHandler;
7+
use Divergence\Models\Media\Media;
8+
use Divergence\Responders\MediaBuilder;
9+
use Exception;
10+
use Psr\Http\Message\ResponseInterface;
11+
12+
class Download extends AbstractMediaEndpoint
13+
{
14+
protected MediaRequestHandler $handler;
15+
16+
public function __construct(MediaRequestHandler $handler)
17+
{
18+
$this->handler = $handler;
19+
}
20+
21+
public function handle(...$arguments): ResponseInterface
22+
{
23+
[$mediaId, $filename] = array_pad($arguments, 2, false);
24+
25+
if (empty($mediaId) || !is_numeric($mediaId)) {
26+
return $this->handler->throwNotFoundError();
27+
}
28+
29+
try {
30+
$Media = Media::getById($mediaId);
31+
} catch (Exception $e) {
32+
return $this->handler->throwUnauthorizedError();
33+
}
34+
35+
if (!$Media) {
36+
return $this->handler->throwNotFoundError();
37+
}
38+
39+
if (!$this->handler->checkReadAccess($Media)) {
40+
return $this->handler->throwUnauthorizedError();
41+
}
42+
43+
$filePath = $Media->getFilesystemPath('original');
44+
$this->handler->responseBuilder = MediaBuilder::class;
45+
$response = $this->handler->respondWithMedia($Media, 'original', $filePath);
46+
47+
return $response->withHeader('Content-Disposition', 'attachment; filename="'.($filename ? $filename : $filePath).'"');
48+
}
49+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace Divergence\Controllers\Media\Endpoints;
4+
5+
use Divergence\Controllers\Media\AbstractMediaEndpoint;
6+
use Divergence\Controllers\MediaRequestHandler;
7+
use Divergence\Models\Media\Media;
8+
use Exception;
9+
use Psr\Http\Message\ResponseInterface;
10+
11+
class Info extends AbstractMediaEndpoint
12+
{
13+
protected MediaRequestHandler $handler;
14+
15+
public function __construct(MediaRequestHandler $handler)
16+
{
17+
$this->handler = $handler;
18+
}
19+
20+
public function handle(...$arguments): ResponseInterface
21+
{
22+
[$mediaID] = $arguments;
23+
24+
if (empty($mediaID) || !is_numeric($mediaID)) {
25+
return $this->handler->throwNotFoundError();
26+
}
27+
28+
try {
29+
$Media = Media::getById($mediaID);
30+
} catch (Exception $e) {
31+
return $this->handler->throwUnauthorizedError();
32+
}
33+
34+
if (!$Media) {
35+
return $this->handler->throwNotFoundError();
36+
}
37+
38+
if (!$this->handler->checkReadAccess($Media)) {
39+
return $this->handler->throwUnauthorizedError();
40+
}
41+
42+
return $this->handler->handleRecordRequest($Media);
43+
}
44+
}

0 commit comments

Comments
 (0)