Skip to content

Commit 6253c9a

Browse files
CopilotDawoudIO
andauthored
refactor: complete middleware consolidation (phase 3) — remaining entity middleware and inline loads (#8176)
Completes the middleware consolidation started in #8166/#8169 by refactoring all five remaining middleware classes to extend `AbstractEntityMiddleware` and eliminating all identified inline entity loads from route handlers. ## Middleware refactors - **`FamilyMiddleware`** / **`PersonMiddleware`** — straightforward extends; removes manual `MiddlewareInterface` boilerplate and legacy `withStatus(412, ...)` error responses - **`PersonMiddleware`** gains an optional `$routeParamName` constructor param (default `'personId'`) to support routes whose path param is named differently (e.g. `{userID}`) - **`EventsMiddleware`** — standardises missing-param response to **412** via `renderErrorJSON` (was a bespoke 400 via `renderJSON`) - **`PropertyMiddleware`** — extends `AbstractEntityMiddleware`; the constructor-injected `$type` filter is applied inside `loadEntity()`: if the loaded property's class doesn't match, `null` is returned and the base class returns a standard 404. No `process()` override needed. - **`UserMiddleware`** — extends `AbstractEntityMiddleware` with a `process()` override that preserves the auth logic: current-user identity shortcut sets the attribute directly from the in-memory user; non-admin requesting another user's data gets a 401; admin path delegates to `parent::process()` for the DB lookup + 404 guard. ```php // UserMiddleware — auth logic preserved, entity load delegated to parent public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $loggedInUser = AuthenticationManager::getCurrentUser(); if ($loggedInUser->getId() == $userId) { return $handler->handle($request->withAttribute('user', $loggedInUser)); // shortcut } if (!$loggedInUser->isAdmin()) { return $response->withStatus(401); } return parent::process($request, $handler); // loads from DB + 404 guard } ``` ## Inline entity load fixes | Route | Was | Now | |---|---|---| | `GET /groups/{groupID}/roles` | `GroupQuery::create()->findOneById($groupID)` | `$request->getAttribute('group')` + `GroupMiddleware` | | `DELETE /groups/{groupID}/removeperson/{userID}` | `PersonQuery::create()->findPk($args['userID'])` | `$request->getAttribute('person')` + `new PersonMiddleware('userID')` | | `POST /groups/{groupID}/addperson/{userID}` | same as above | same fix | | `GET /api/user/{userId}/permissions` | `UserQuery::create()->findPk($userId)` (redundant — `UserMiddleware` already on the group) | `$request->getAttribute('user')` | Removes now-unused `PersonQuery` import from `people-groups.php` and `UserQuery` import from `user-admin.php`. <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>refactor: complete middleware consolidation (phase 3) — remaining entity middleware and inline loads</issue_title> > <issue_description>## Context > > PR #8166 introduced `AbstractEntityMiddleware`, `InputSanitizationMiddleware`, and `RequestParameterValidationMiddleware`. > PR #8169 refactored `GroupMiddleware`, `DepositMiddleware`, `CalendarMiddleware`, and `KioskDeviceMiddleware` to extend `AbstractEntityMiddleware`, and wired entity middleware to 17 routes. > > This issue tracks the **remaining work** to complete the consolidation. > > --- > > ## 1 — Middleware files not yet extending `AbstractEntityMiddleware` > > | File | Route param | Attribute | Notes | > |------|-------------|-----------|-------| > | `Slim/Middleware/Api/FamilyMiddleware.php` | `familyId` | `family` | Straightforward refactor | > | `Slim/Middleware/Api/PersonMiddleware.php` | `personId` | `person` | Straightforward refactor | > | `Slim/Middleware/EventsMiddleware.php` | `id` | `event` | Returns **400** (not 412) for missing param — needs `getNotFoundMessage()` + consider moving to `Api/` | > | `Slim/Middleware/Api/UserMiddleware.php` | `userId` | `user` | Has additional auth checks alongside entity loading; evaluate separation | > | `Slim/Middleware/Api/PropertyMiddleware.php` | `propertyId` | `property` | Has constructor-injected type parameter; needs factory pattern or override | > > > **Skip**: `PublicCalendarMiddleware` — loads by access token (not route param), sets multiple attributes; not a simple PK load. > > --- > > ## 2 — Inline entity loads still in route handlers > > | Route | File | Line | Entity loaded | Middleware exists? | > |-------|------|------|---------------|--------------------| > | `GET /{groupID}/roles` | `api/routes/people/people-groups.php` | ~197 | `Group` via `GroupQuery::create()->findOneById($groupID)` | Yes — `GroupMiddleware` | > | `DELETE /{groupID}/removeperson/{userID}` | `api/routes/people/people-groups.php` | ~298 | `Person` via `PersonQuery::create()->findPk($userID)` | Yes — `PersonMiddleware` | > | `POST /{groupID}/addperson/{userID}` | `api/routes/people/people-groups.php` | ~337 | `Person` via `PersonQuery::create()->findPk($userID)` | Yes — `PersonMiddleware` | > | `GET /api/user/{userId}/permissions` | `admin/routes/api/user-admin.php` | ~135 | `User` via `UserQuery::create()->findPk($userId)` | Yes — `UserMiddleware` | > > --- > > ## Suggested order > > 1. `FamilyMiddleware` and `PersonMiddleware` — simple extends, unblock the inline-load fixes > 2. Fix inline loads in `people-groups.php` (after `PersonMiddleware` is refactored) > 3. Fix inline load in `user-admin.php` (after `UserMiddleware` is evaluated) > 4. `EventsMiddleware` — confirm 400 vs 412 is intentional, then refactor + consider namespace move > 5. `UserMiddleware` and `PropertyMiddleware` — more complex; separate issues if needed</issue_description> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > </comments> > </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #8170 <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey). --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DawoudIO <554959+DawoudIO@users.noreply.github.com> Co-authored-by: George Dawoud <george@dawouds.com>
1 parent bf25256 commit 6253c9a

File tree

15 files changed

+226
-220
lines changed

15 files changed

+226
-220
lines changed

cypress/e2e/api/private/standard/private.people.family.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ describe("API Private Family", () => {
8888
});
8989

9090
it("Returns error for non-existent family", () => {
91-
// API returns 412 Precondition Failed for non-existent family
92-
cy.makePrivateAdminAPICall("GET", "/api/family/99999", null, 412);
91+
// AbstractEntityMiddleware returns 404 Not Found for missing entity
92+
cy.makePrivateAdminAPICall("GET", "/api/family/99999", null, 404);
9393
});
9494
});
9595

cypress/e2e/api/private/standard/private.people.person.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ describe("API Private Person", () => {
2222
});
2323

2424
it("Returns error for non-existent person", () => {
25-
// API returns 412 Precondition Failed for non-existent person
26-
cy.makePrivateAdminAPICall("GET", "/api/person/99999", null, 412);
25+
// AbstractEntityMiddleware returns 404 Not Found for missing entity
26+
cy.makePrivateAdminAPICall("GET", "/api/person/99999", null, 404);
2727
});
2828
});
2929

rector.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
<?php
22

3-
declare(strict_types=1);
4-
53
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
64
use Rector\Config\RectorConfig;
75
use Rector\Set\ValueObject\LevelSetList;

src/ChurchCRM/Slim/Middleware/Api/DepositMiddleware.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
<?php
22

3-
declare(strict_types=1);
4-
53
namespace ChurchCRM\Slim\Middleware\Api;
64

75
use ChurchCRM\model\ChurchCRM\DepositQuery;

src/ChurchCRM/Slim/Middleware/Api/FamilyMiddleware.php

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,26 @@
33
namespace ChurchCRM\Slim\Middleware\Api;
44

55
use ChurchCRM\model\ChurchCRM\FamilyQuery;
6-
use ChurchCRM\Slim\SlimUtils;
76

8-
use Psr\Http\Message\ServerRequestInterface;
9-
use Psr\Http\Server\RequestHandlerInterface;
10-
use Psr\Http\Server\MiddlewareInterface;
11-
use Psr\Http\Message\ResponseInterface;
12-
use Laminas\Diactoros\Response;
13-
14-
class FamilyMiddleware implements MiddlewareInterface
7+
class FamilyMiddleware extends AbstractEntityMiddleware
158
{
16-
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
9+
protected function getRouteParamName(): string
1710
{
18-
$response = new Response();
19-
$familyId = SlimUtils::getRouteArgument($request, 'familyId');
20-
if (empty(trim($familyId))) {
21-
return $response->withStatus(412, gettext('Missing') . ' FamilyId');
22-
}
11+
return 'familyId';
12+
}
2313

24-
$family = FamilyQuery::create()->findPk($familyId);
25-
if (empty($family)) {
26-
return $response->withStatus(412, 'FamilyId: ' . $familyId . ' ' . gettext('not found'));
27-
}
14+
protected function getAttributeName(): string
15+
{
16+
return 'family';
17+
}
2818

29-
$request = $request->withAttribute('family', $family);
19+
protected function loadEntity(string $id): mixed
20+
{
21+
return FamilyQuery::create()->findPk($id);
22+
}
3023

31-
return $handler->handle($request);
24+
protected function getNotFoundMessage(): string
25+
{
26+
return gettext('Family not found');
3227
}
3328
}

src/ChurchCRM/Slim/Middleware/Api/GroupMiddleware.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
<?php
22

3-
declare(strict_types=1);
4-
53
namespace ChurchCRM\Slim\Middleware\Api;
64

75
use ChurchCRM\model\ChurchCRM\GroupQuery;

src/ChurchCRM/Slim/Middleware/Api/KioskDeviceMiddleware.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
<?php
22

3-
declare(strict_types=1);
4-
53
namespace ChurchCRM\Slim\Middleware\Api;
64

75
use ChurchCRM\model\ChurchCRM\KioskDeviceQuery;

src/ChurchCRM/Slim/Middleware/Api/PersonMiddleware.php

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,28 @@
33
namespace ChurchCRM\Slim\Middleware\Api;
44

55
use ChurchCRM\model\ChurchCRM\PersonQuery;
6-
use ChurchCRM\Slim\SlimUtils;
7-
use Laminas\Diactoros\Response;
8-
use Psr\Http\Message\ServerRequestInterface;
9-
use Psr\Http\Server\RequestHandlerInterface;
10-
use Psr\Http\Server\MiddlewareInterface;
11-
use Psr\Http\Message\ResponseInterface;
126

13-
class PersonMiddleware implements MiddlewareInterface
7+
class PersonMiddleware extends AbstractEntityMiddleware
148
{
15-
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
9+
public function __construct(private readonly string $routeParamName = 'personId') {}
10+
11+
protected function getRouteParamName(): string
1612
{
17-
$response = new Response();
18-
$personId = SlimUtils::getRouteArgument($request, 'personId');
19-
if (empty(trim($personId))) {
20-
return $response->withStatus(412, gettext('Missing') . ' PersonId');
21-
}
13+
return $this->routeParamName;
14+
}
2215

23-
$person = PersonQuery::create()->findPk($personId);
24-
if (empty($person)) {
25-
return $response->withStatus(412, 'PersonId : ' . $personId . ' ' . gettext('not found'));
26-
}
16+
protected function getAttributeName(): string
17+
{
18+
return 'person';
19+
}
2720

28-
$request = $request->withAttribute('person', $person);
21+
protected function loadEntity(string $id): mixed
22+
{
23+
return PersonQuery::create()->findPk($id);
24+
}
2925

30-
return $handler->handle($request);
26+
protected function getNotFoundMessage(): string
27+
{
28+
return gettext('Person not found');
3129
}
3230
}
Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,37 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
namespace ChurchCRM\Slim\Middleware\Api;
46

57
use ChurchCRM\model\ChurchCRM\PropertyQuery;
6-
use ChurchCRM\Slim\SlimUtils;
7-
use ChurchCRM\Utils\LoggerUtils;
8-
9-
use Laminas\Diactoros\Response;
10-
use Psr\Http\Message\ServerRequestInterface;
11-
use Psr\Http\Server\RequestHandlerInterface;
12-
use Psr\Http\Server\MiddlewareInterface;
13-
use Psr\Http\Message\ResponseInterface;
148

15-
class PropertyMiddleware implements MiddlewareInterface
9+
class PropertyMiddleware extends AbstractEntityMiddleware
1610
{
17-
protected string $type;
11+
public function __construct(private readonly string $type) {}
1812

19-
public function __construct(string $type)
13+
protected function getRouteParamName(): string
2014
{
21-
$this->type = $type;
15+
return 'propertyId';
2216
}
2317

24-
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
18+
protected function getAttributeName(): string
2519
{
26-
$propertyId = SlimUtils::getRouteArgument($request, 'propertyId');
27-
$response = new Response();
28-
if (empty(trim($propertyId))) {
29-
return $response->withStatus(412, gettext('Missing') . ' PropertyId');
30-
}
31-
32-
$property = PropertyQuery::create()->findPk($propertyId);
33-
34-
if (empty($property)) {
35-
LoggerUtils::getAppLogger()->debug('Pro Type is ' . $property->getPropertyType()->getPrtClass() . ' Looking for ' . $this->type);
20+
return 'property';
21+
}
3622

37-
return $response->withStatus(412, 'PropertyId : ' . $propertyId . ' ' . gettext('not found'));
38-
} elseif ($property->getPropertyType()->getPrtClass() != $this->type) {
39-
return $response->withStatus(500, 'PropertyId : ' . $propertyId . ' ' . gettext(' has a type mismatch'));
23+
protected function loadEntity(string $id): mixed
24+
{
25+
$property = PropertyQuery::create()->findPk($id);
26+
if ($property !== null && $property->getPropertyType()->getPrtClass() !== $this->type) {
27+
return null;
4028
}
4129

42-
$request = $request->withAttribute('property', $property);
30+
return $property;
31+
}
4332

44-
return $handler->handle($request);
33+
protected function getNotFoundMessage(): string
34+
{
35+
return gettext('Property not found');
4536
}
4637
}

src/ChurchCRM/Slim/Middleware/Api/UserMiddleware.php

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,51 @@
55
use ChurchCRM\Authentication\AuthenticationManager;
66
use ChurchCRM\model\ChurchCRM\UserQuery;
77
use ChurchCRM\Slim\SlimUtils;
8-
98
use Laminas\Diactoros\Response;
9+
use Psr\Http\Message\ResponseInterface;
1010
use Psr\Http\Message\ServerRequestInterface;
1111
use Psr\Http\Server\RequestHandlerInterface;
12-
use Psr\Http\Server\MiddlewareInterface;
13-
use Psr\Http\Message\ResponseInterface;
1412

15-
class UserMiddleware implements MiddlewareInterface
13+
class UserMiddleware extends AbstractEntityMiddleware
1614
{
15+
protected function getRouteParamName(): string
16+
{
17+
return 'userId';
18+
}
19+
20+
protected function getAttributeName(): string
21+
{
22+
return 'user';
23+
}
24+
25+
protected function loadEntity(string $id): mixed
26+
{
27+
return UserQuery::create()->findPk($id);
28+
}
29+
30+
protected function getNotFoundMessage(): string
31+
{
32+
return gettext('User not found');
33+
}
34+
1735
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
1836
{
1937
$response = new Response();
20-
$userId = SlimUtils::getRouteArgument($request, 'userId');
38+
$userId = SlimUtils::getRouteArgument($request, $this->getRouteParamName());
39+
2140
if (empty(trim($userId))) {
22-
return $response->withStatus(412, gettext('Missing') . ' UserId');
41+
return SlimUtils::renderErrorJSON($response, gettext('Missing') . ' ' . $this->getRouteParamName(), [], 412);
2342
}
2443

2544
$loggedInUser = AuthenticationManager::getCurrentUser();
2645
if ($loggedInUser->getId() == $userId) {
27-
$user = $loggedInUser;
28-
} elseif ($loggedInUser->isAdmin()) {
29-
$user = UserQuery::create()->findPk($userId);
30-
if (empty($user)) {
31-
return $response->withStatus(412, 'User : ' . $userId . ' ' . gettext('not found'));
32-
}
33-
} else {
34-
return $response->withStatus(401);
46+
return $handler->handle($request->withAttribute($this->getAttributeName(), $loggedInUser));
3547
}
3648

37-
$request = $request->withAttribute('user', $user);
49+
if (!$loggedInUser->isAdmin()) {
50+
return $response->withStatus(401);
51+
}
3852

39-
return $handler->handle($request);
53+
return parent::process($request, $handler);
4054
}
4155
}

0 commit comments

Comments
 (0)