Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/tests-unit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Run Unit Tests

on:
pull_request:
types:
- "opened"
- "reopened"
- "synchronize"
branches:
- master

jobs:
phpunit_test:
name: "Unit Test"
runs-on: ubuntu-22.04
env:
XDEBUG_MODE: coverage
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Docker Compose Up
run: docker compose up -d

- name: Docker Compose PS
run: docker compose ps

- name: Run Composer Install
run: docker exec persistables-php-1 composer install

- name: Run Unit Tests
run: docker exec persistables-php-1 vendor/bin/phpunit

- name: Install Xmllint
run: sudo apt-get update && sudo apt-get install -y libxml2-utils

- name: Check Coverage Threshold
run: |
FILE=clover/clover.xml

if [ ! -f "$FILE" ]; then
echo "❌ Coverage report not found at $FILE"
exit 1
fi

COVERED=$(xmllint --xpath 'string(//project/metrics/@coveredstatements)' "$FILE")
TOTAL=$(xmllint --xpath 'string(//project/metrics/@statements)' "$FILE")

if [ -z "$COVERED" ] || [ -z "$TOTAL" ] || [ "$TOTAL" -eq 0 ]; then
echo "⚠️ Error parsing coverage"
exit 1
fi
PERCENT=$(awk "BEGIN {printf \"%.2f\", ($COVERED/$TOTAL) * 100 }")

# Check threshold
THRESHOLD=90
if (( $(awk "BEGIN {print ($PERCENT < $THRESHOLD)}") )); then
echo "❌ Coverage $PERCENT% is below required $THRESHOLD%"
exit 1
else
echo "✅ Coverage $PERCENT% meets threshold"
fi

3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ Thumbs.db
*.cache

# Coverage reports
coverage/
/tests/coverage/
/clover/

# Build artifacts
/build/
Expand Down
13 changes: 5 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Subtext\Persistables
![Run Unit Tests](https://github.com/subtext/persistables/actions/workflows/tests-unit.yml/badge.svg)

A lightweight PHP library designed to abstract and unify the persistence of
domain objects across SQL databases. Inspired by the principles of ORMs, but
Expand All @@ -23,10 +24,12 @@ namespace Subtext\Persistables;
#[Table(name: 'users', primaryKey: 'userId')]
class User extends Persistable
{
#[Column(name: 'user_id', primary: true)]
#[Column(name: 'user_id')]
protected ?int $userId = null;

#[Column(name: 'user_name')]
protected ?string $userName = null;

#[Column(name: 'email_address')]
protected ?string $email = null;

Expand Down Expand Up @@ -74,12 +77,6 @@ class User extends Persistable
'userName' => $this->getUserName(),
'email' => $this->getEmail(),
];
}

public function getPersistables(): ?Collection
{
return null;
}

}
}
```
23 changes: 23 additions & 0 deletions debug.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.2/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
executionOrder="depends,defects"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
failOnWarning="true">
<testsuites>
<testsuite name="default">
<directory>tests/unit</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
</source>
<php>
<ini name="date.timezone" value="UTC" />
</php>
</phpunit>
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ services:
DB_HOST: ${DB_HOST}
DB_PASS: ${DB_PASS}
DB_NAME: ${DB_NAME}
XDEBUG_MODE: ${XDEBUG_MODE}
volumes:
- ./:/app
working_dir: /app
Expand Down
29 changes: 29 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.2/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
executionOrder="depends,defects"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
failOnWarning="true">
<testsuites>
<testsuite name="default">
<directory>tests/unit</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
</source>
<coverage includeUncoveredFiles="true">
<report>
<html outputDirectory="tests/coverage" />
<clover outputFile="clover/clover.xml" />
</report>
</coverage>
<php>
<ini name="date.timezone" value="UTC" />
</php>
</phpunit>
2 changes: 1 addition & 1 deletion src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ abstract public function getEntityClass(): string;

protected function validate(mixed $value): void
{
if (!is_object($value) || !($value::class === $this->getEntityClass())) {
if (!is_object($value) || !(is_a($value, $this->getEntityClass()))) {
throw new InvalidArgumentException(sprintf(
"Value must be an instance of %s",
$this->getEntityClass()
Expand Down
23 changes: 23 additions & 0 deletions src/Container.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Subtext\Persistables;

use InvalidArgumentException;

class Container extends Collection {
public function getEntityClass(): string
{
return Collection::class;
}

protected function validate(mixed $value): void
{
if (!($value instanceof Collection || $value instanceof Persistable)) {
throw new InvalidArgumentException(sprintf(
'Value must be an instance of %s or %s',
Collection::class,
Persistable::class
));
}
}
};
57 changes: 46 additions & 11 deletions src/Databases/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,58 @@

namespace Subtext\Persistables\Databases;

use Closure;
use PDO;
use TypeError;
use Subtext\Persistables\Databases\SqlGenerators\MySqlGenerator;

interface Connection
class Connection
{
/**
* Singleton creator
*
* @return self
* A container for configuring the PDO object. Pass in all of the parameters
* and use them as arguments for a callable
* @param string $database The database name.
* @param string $hostname The database host url.
* @param string $username The database username.
* @param string $password The database password.
* @param string $driver A compatible PDO driver string name.
* @param string $charset The character set to pass to the PDO object.
* @param Closure $fn A function which accepts each of the previous
* arguments and returns a PDO object.
*/
public static function getInstance(): self;
public function __construct(
private readonly string $database,
private readonly string $hostname,
private readonly string $username,
private readonly string $password,
private readonly string $driver,
private readonly string $charset,
private readonly Closure $fn
) {}

public function getPdo(): ?PDO
{
$pdo = null;
if ($this->fn) {
$pdo = ($this->fn)(
$this->database,
$this->hostname,
$this->username,
$this->password,
$this->driver,
$this->charset,
);
}
return $pdo;
}

/**
* @return PDO
* @throws TypeError if PDO is null
* Currently only mysql is supported. Other database drivers will be added
* in the future.
*
* @return SqlGenerator
*/
public function getPdo(): PDO;

public function getSqlGenerator(): SqlGenerator;
public function getSqlGenerator(): SqlGenerator
{
return MySqlGenerator::getInstance();
}
}
63 changes: 0 additions & 63 deletions src/Databases/Connections/MySql.php

This file was deleted.

13 changes: 13 additions & 0 deletions src/Databases/Error.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Subtext\Persistables\Databases;

class Error
{
public function __construct(
public string $msg,
public int $code,
public string $info
) {
}
}
12 changes: 3 additions & 9 deletions src/Databases/Meta/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

class Factory
{
public const array ACCESSOR = ['get', 'set'];
private static ?self $instance = null;
private Collection $meta;

Expand All @@ -30,9 +29,9 @@ private function __construct()
$this->meta = new Collection();
}

public static function getInstance(): self
public static function getInstance(bool $new = false): self
{
if (self::$instance === null) {
if (self::$instance === null || $new) {
self::$instance = new self();
}
return self::$instance;
Expand Down Expand Up @@ -185,11 +184,6 @@ private function parseAccessor(

private function inferAccessorName(string $property, string $class, string $type = 'get'): ?string
{
if (!in_array($type, self::ACCESSOR)) {
throw new InvalidArgumentException(
'Type argument must be one of ' . implode(', ', self::ACCESSOR) . '.'
);
}
$method = $type . ucfirst($property);
$this->validateMethod($class, $method);
return $method;
Expand All @@ -209,7 +203,7 @@ private function validateMethod(string $class, string $method): void
private function validateClass(string $class): void
{
if (class_exists($class)) {
if (is_subclass_of($class, Persistable::class) || is_subclass_of($class, Collection::class)) {
if (is_subclass_of($class, Persistable::class) || is_subclass_of($class, PersistableCollection::class)) {
return;
}
}
Expand Down
Loading