# Mezzio Example: Doctrine Entities and Repositories

## Overview

This post brings several pieces of supporting libraries together.

### Entities to Support OAuth2 Requirements

We’ll start by defining a minimum domain model via PHP8 Attributes and Doctrine’s new attribute mapping driver. In the short-term, we only need to model what’s necessary for our application’s authentication layer using OAuth2. For now, we’re creating a minimal set of objects to comply with the needs of our supporting libraries' interfaces. We need a minimum of 6 entities and their corresponding repositories:

• User
• OAuthClient
• OAuthAuthCode
• OAuthAccessToken
• OAuthRefreshToken
• OAuthScope

Mezzio includes an implementation of Alex Bilbie’s wonderful, spec-compliant OAuth2 Server as an adapter to the Mezzio authentication abstraction middleware component. Both the server library and the Mezzio authentication component provide various interfaces for which we’ll need to provide concrete implementations. We’ll do that while building our domain model with Entities and Repositories.

### Psalm Changes Required for Error Level 1

#### Entity Changes

TLDR; All errors required suppression notation to pass. Check this diff for the details: https://github.com/marcguyer/mezzio-doctrine-oauth2-example/commit/5640b45bb942d577af0f4e3606f30bc91b6105c5

## Entities

An Entity is a persist-able PHP data object with an identity. Generally speaking, an entity represents a database table as a native PHP object with properties that correspond with the table columns.

We’ll create a set of entities fulfilling the minimum requirements of both Mezzio Authentication and the corresponding OAuth2 library.

### User Entity

A User represents an unique individual person in our domain model. The User entity will need to implement both the OAuth2 Server library user interface and Mezzio Authentication component’s user interface:

#### Implemented Interfaces

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21   * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities; interface UserEntityInterface { /** * Return the user's identifier. * * @return mixed */ public function getIdentifier(); } 

### OAuthRefreshToken Entity

#### Implemented Interfaces

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58   * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities; use DateTimeImmutable; interface RefreshTokenEntityInterface { /** * Get the token's identifier. * * @return string */ public function getIdentifier(); /** * Set the token's identifier. * * @param mixed $identifier */ public function setIdentifier($identifier); /** * Get the token's expiry date time. * * @return DateTimeImmutable */ public function getExpiryDateTime(); /** * Set the date time when the token expires. * * @param DateTimeImmutable $dateTime */ public function setExpiryDateTime(DateTimeImmutable$dateTime); /** * Set the access token that the refresh token was associated with. * * @param AccessTokenEntityInterface $accessToken */ public function setAccessToken(AccessTokenEntityInterface$accessToken); /** * Get the access token that the refresh token was originally associated with. * * @return AccessTokenEntityInterface */ public function getAccessToken(); } 

## Repositories

### User Repository

The User repository will need to implement both the OAuth2 Server library user repository interface and Mezzio Authentication component’s user repository interface:

#### Implemented Interfaces

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34   * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; interface UserRepositoryInterface extends RepositoryInterface { /** * Get a user entity. * * @param string $username * @param string$password * @param string $grantType The grant type used * @param ClientEntityInterface$clientEntity * * @return UserEntityInterface|null */ public function getUserEntityByUserCredentials( $username,$password, $grantType, ClientEntityInterface$clientEntity ); } 
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24  

### OAuthAuthCode Repository

The AuthCode repository will need to implement only the OAuth2 Server library authcode repository interface:

#### Implemented Interfaces

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52   * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; /** * Auth code storage interface. */ interface AuthCodeRepositoryInterface extends RepositoryInterface { /** * Creates a new AuthCode * * @return AuthCodeEntityInterface */ public function getNewAuthCode(); /** * Persists a new auth code to permanent storage. * * @param AuthCodeEntityInterface $authCodeEntity * * @throws UniqueTokenIdentifierConstraintViolationException */ public function persistNewAuthCode(AuthCodeEntityInterface$authCodeEntity); /** * Revoke an auth code. * * @param string $codeId */ public function revokeAuthCode($codeId); /** * Check if the auth code has been revoked. * * @param string $codeId * * @return bool Return true if this code has been revoked */ public function isAuthCodeRevoked($codeId); } 

#### Implementation

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60  getEntityManager()->persist($authCodeEntity);$this->getEntityManager()->flush(); } /** * @throws ORM\OptimisticLockException * @throws ORM\ORMException * * @return void * * @param string $codeId */ public function revokeAuthCode($codeId) { /** @var ?OAuthAuthCode $authCodeEntity */$authCodeEntity = $this->find($codeId); if (null === $authCodeEntity) { return; }$authCodeEntity->setIsRevoked(true); $this->getEntityManager()->persist($authCodeEntity); $this->getEntityManager()->flush(); } public function isAuthCodeRevoked($codeId): bool { /** @var ?OAuthAuthCode $authCodeEntity */$authCodeEntity = $this->find($codeId); if (null === $authCodeEntity) { return true; } return$authCodeEntity->getIsRevoked(); } } 

### OAuthAccessToken Repository

The AccessToken repository will need to implement only the OAuth2 Server library accesstoken repository interface:

#### Implemented Interfaces

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58   * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; /** * Access token interface. */ interface AccessTokenRepositoryInterface extends RepositoryInterface { /** * Create a new access token * * @param ClientEntityInterface $clientEntity * @param ScopeEntityInterface[]$scopes * @param mixed $userIdentifier * * @return AccessTokenEntityInterface */ public function getNewToken(ClientEntityInterface$clientEntity, array $scopes,$userIdentifier = null); /** * Persists a new access token to permanent storage. * * @param AccessTokenEntityInterface $accessTokenEntity * * @throws UniqueTokenIdentifierConstraintViolationException */ public function persistNewAccessToken(AccessTokenEntityInterface$accessTokenEntity); /** * Revoke an access token. * * @param string $tokenId */ public function revokeAccessToken($tokenId); /** * Check if the access token has been revoked. * * @param string $tokenId * * @return bool Return true if this token has been revoked */ public function isAccessTokenRevoked($tokenId); } 

#### Implementation

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78  setClient($clientEntity); foreach ($scopes as $scope) {$accessToken->addScope($scope); } if ($userIdentifier !== null) { $accessToken->setUser($this->getEntityManager()->getReference(User::class, $userIdentifier)); } return$accessToken; } /** * @return void * @throws ORMException * * @throws OptimisticLockException */ public function persistNewAccessToken(OAuth\Entities\AccessTokenEntityInterface $accessTokenEntity) {$this->getEntityManager()->persist($accessTokenEntity);$this->getEntityManager()->flush(); } /** * @param string $tokenId * * @return void * @throws ORMException * */ public function revokeAccessToken($tokenId) { /** @var ?OAuthAccessToken $accessTokenEntity */$accessTokenEntity = $this->findOneBy(['token' =>$tokenId]); if (null === $accessTokenEntity) { return; }$accessTokenEntity->setIsRevoked(true); $this->getEntityManager()->persist($accessTokenEntity); $this->getEntityManager()->flush(); } public function isAccessTokenRevoked($tokenId): bool { /** @var ?OAuthAccessToken $accessTokenEntity */$accessTokenEntity = $this->findOneBy(['token' =>$tokenId]); if (null === $accessTokenEntity) { return true; } return$accessTokenEntity->getIsRevoked(); } } 

### OAuthRefreshToken Repository

The RefreshToken repository will need to implement only the OAuth2 Server library refreshtoken repository interface:

#### Implemented Interfaces

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52   * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; /** * Refresh token interface. */ interface RefreshTokenRepositoryInterface extends RepositoryInterface { /** * Creates a new refresh token * * @return RefreshTokenEntityInterface|null */ public function getNewRefreshToken(); /** * Create a new refresh token_name. * * @param RefreshTokenEntityInterface $refreshTokenEntity * * @throws UniqueTokenIdentifierConstraintViolationException */ public function persistNewRefreshToken(RefreshTokenEntityInterface$refreshTokenEntity); /** * Revoke the refresh token. * * @param string $tokenId */ public function revokeRefreshToken($tokenId); /** * Check if the refresh token has been revoked. * * @param string $tokenId * * @return bool Return true if this token has been revoked */ public function isRefreshTokenRevoked($tokenId); } 

#### Implementation

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61  getEntityManager(); $em->persist($refreshTokenEntity); $em->flush(); } /** * @throws OptimisticLockException * @throws ORMException */ public function revokeRefreshToken($tokenId): void { /** @var ?OAuthRefreshToken $refreshTokenEntity */$refreshTokenEntity = $this->find($tokenId); if (null === $refreshTokenEntity) { return; }$refreshTokenEntity->setIsRevoked(true); $em =$this->getEntityManager(); $em->persist($refreshTokenEntity); $em->flush(); } public function isRefreshTokenRevoked($tokenId): bool { /** @var ?OAuthRefreshToken $refreshTokenEntity */$refreshTokenEntity = $this->find($tokenId); if (null === $refreshTokenEntity) { return true; } return$refreshTokenEntity->getIsRevoked(); } } 

### OAuthScope Repository

The Scope repository will need to implement only the OAuth2 Server library scope repository interface:

#### Implemented Interfaces

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47   * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; /** * Scope interface. */ interface ScopeRepositoryInterface extends RepositoryInterface { /** * Return information about a scope. * * @param string $identifier The scope identifier * * @return ScopeEntityInterface|null */ public function getScopeEntityByIdentifier($identifier); /** * Given a client, grant type and optional user identifier validate the set of scopes requested are valid and optionally * append additional scopes or remove requested scopes. * * @param ScopeEntityInterface[] $scopes * @param string$grantType * @param ClientEntityInterface $clientEntity * @param null|string$userIdentifier * * @return ScopeEntityInterface[] */ public function finalizeScopes( array $scopes,$grantType, ClientEntityInterface $clientEntity,$userIdentifier = null ); } 

#### Implementation

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30  findOneBy(['scope' => $identifier]); return$scope; } public function finalizeScopes( array $scopes,$grantType, OAuth\Entities\ClientEntityInterface $clientEntity,$userIdentifier = null ): array { return \$scopes; } } 

#### Series: Mezzio Example

1. Mezzio Example: Introduction
2. Mezzio Example: Functional and Unit Testing
3. Mezzio Example: Psalm Introduction
4. Mezzio Example: Doctrine Entities and Repositories