Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PHP 8.0 | 8.1 support with tests coverage #45

Closed
wants to merge 6 commits into from
Closed
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
46 changes: 46 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# GitHub Action
name: Tests
on: [push, pull_request]

jobs:
tests:
name: Tests PHP ${{ matrix.php-versions }}
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: [ubuntu-latest]
php-versions: ['8.1']
steps:
- name: Checkout
uses: actions/checkout@v3

# Docs: https://github.com/shivammathur/setup-php
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
coverage: xdebug

- name: Get Composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"

- name: Cache Composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-

- name: Install dependencies
run: composer install --no-progress --prefer-dist --no-interaction --optimize-autoloader

- name: Run unit tests
run: vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover=report/logs/clover.xml

- name: Upload coverage results to Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
composer global require php-coveralls/php-coveralls -n
php-coveralls --coverage_clover=report/logs/clover.xml --json_path=report/logs/coveralls-upload.json -v
12 changes: 6 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@
}
],
"require": {
"php": "^7.2",
"php": "^8.1",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
"monolog/monolog": "^2.0"
"monolog/monolog": "^2.0 || ^3.0"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"doctrine/dbal": "^2.10",
"psy/psysh": "^0.10.4",
"fzaninotto/faker": "^1.9"
"phpunit/phpunit": "^9.5",
"doctrine/dbal": "^3.5",
"psy/psysh": "^0.11.10",
"fakerphp/faker": "^1.21"
},
"autoload": {
"psr-4": {
Expand Down
18 changes: 0 additions & 18 deletions phpunit.xml

This file was deleted.

13 changes: 13 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true" cacheResult="false">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Test">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
</phpunit>
37 changes: 19 additions & 18 deletions src/MySQLHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
namespace MySQLHandler;

use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
use Monolog\Level;
use Monolog\LogRecord;
use PDO;
use PDOStatement;

Expand Down Expand Up @@ -51,15 +52,15 @@ class MySQLHandler extends AbstractProcessingHandler
* @param string $table Table in the database to store the logs in
* @param array $additionalFields Additional Context Parameters to store in database
* @param bool $initialize Defines whether attempts to alter database should be skipped
* @param bool|int $level Debug level which this handler should store
* @param int|string|Level $level Debug level which this handler should store
* @param bool $bubble
*/
public function __construct(
PDO $pdo,
string $table,
array $additionalFields = [],
bool $initialize = false,
int $level = Logger::DEBUG,
int|string|Level $level = Level::Debug,
bool $bubble = true
) {
parent::__construct($level, $bubble);
Expand All @@ -76,13 +77,13 @@ private function initialize(): void
{
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS `{$this->mySQLRecord->getTable()}` (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
channel VARCHAR(255),
level INTEGER,
message LONGTEXT,
time INTEGER UNSIGNED,
INDEX(channel) USING HASH,
INDEX(level) USING HASH,
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
channel VARCHAR(255),
level INTEGER,
message LONGTEXT,
time INTEGER UNSIGNED,
INDEX(channel) USING HASH,
INDEX(level) USING HASH,
INDEX(time) USING BTREE
);
");
Expand Down Expand Up @@ -123,7 +124,7 @@ private function initialize(): void
}

/**
* Prepare the sql statment depending on the fields that should be written to the database
* Prepare the sql statement depending on the fields that should be written to the database
* @param array $content
*/
private function prepareStatement(array $content): void
Expand Down Expand Up @@ -155,10 +156,10 @@ private function prepareStatement(array $content): void
/**
* Writes the record down to the log of the implementing handler
*
* @param array $record
* @param LogRecord $record
* @return void
*/
protected function write(array $record): void
protected function write(LogRecord $record): void
{
if (! $this->initialized) {
$this->initialize();
Expand All @@ -169,19 +170,19 @@ protected function write(array $record): void
* getting added to $record['extra']
* @see https://github.com/Seldaek/monolog/blob/master/doc/02-handlers-formatters-processors.md
*/
if (isset($record['extra'])) {
$record['context'] = array_merge($record['context'], $record['extra']);
}

$content = $this->mySQLRecord->filterContent(array_merge([
'channel' => $record['channel'],
'level' => $record['level'],
'message' => $record['message'],
'time' => $record['datetime']->format('U'),
], $record['context']));
], $record['context'], $record['extra']));

$this->prepareStatement($content);

if (array_key_exists('id', $content)) {
unset($content['id']);
}

$this->statement->execute($content);
}
}
198 changes: 198 additions & 0 deletions tests/MySQLHandlerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<?php

declare(strict_types=1);

namespace Tests;

use Faker\Factory;
use Monolog\Level;
use Monolog\LogRecord;
use MySQLHandler\MySQLHandler;
use MySQLHandler\MySQLRecord;
use PHPUnit\Framework\TestCase;

/**
* Class MySQLHandlerTest
* @package Tests
*/
class MySQLHandlerTest extends TestCase
{
protected static $createSql = "
CREATE TABLE IF NOT EXISTS `%s` (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
channel VARCHAR(255),
level INTEGER,
message LONGTEXT,
time INTEGER UNSIGNED,
INDEX(channel) USING HASH,
INDEX(level) USING HASH,
INDEX(time) USING BTREE
);
";

/**
* @test
* @return void
* @dataProvider provideFakeData
*/
public function test_handle(
string $expectedInsert,
array $expectedParams,
array $initialColumns,
LogRecord $record,
string $table,
array $additionalFields = [],
bool $initialize = false,
int|string|Level $level = Level::Debug,
bool $bubble = true
): void {
$pdo = $this->createMock(\PDO::class);
$columnsStmt = $this->createMock(\PDOStatement::class);
$insertStmt = $this->createMock(\PDOStatement::class);

$isHandling = $record['level'] >= $level;
$expectedResult = (!$isHandling) ? false : false === $bubble;
$mysqlRecord = new MySQLRecord($table, $additionalFields);
$addedColumns = array_diff($additionalFields, $initialColumns);
$removedColumns = array_diff($initialColumns, $mysqlRecord->getDefaultColumns(), $additionalFields);

if ($isHandling) {
if (!$initialize) {
$pdo->expects($this->exactly(1 + count($removedColumns) + count($addedColumns)))
->method('exec')
->withConsecutive(
[sprintf(static::$createSql, $table)],
...array_map(static function (string $c) use ($table) {
return ["ALTER TABLE `{$table}` DROP `{$c}`;"];
}, $removedColumns),
...array_map(static function (string $c) use ($table) {
return ["ALTER TABLE `{$table}` ADD `{$c}` TEXT NULL DEFAULT NULL;"];
}, $addedColumns)
)
->willReturn(0);

$pdo->expects($this->once())
->method('query')
->with("SELECT * FROM `{$table}` LIMIT 0;")
->willReturn($columnsStmt);
$columnsStmt->expects($this->exactly(count($initialColumns) + 1))
->method('columnCount')
->willReturn(count($initialColumns));

$columnsStmt->expects($this->exactly(count($initialColumns)))
->method('getColumnMeta')
->willReturn(...array_map(static function (string $c) {
return ['name' => $c];
}, $initialColumns));
} else {
$pdo->expects($this->never())->method('exec');
$pdo->expects($this->never())->method('query');
}

$pdo->expects($this->once())
->method('prepare')
->with($expectedInsert)
->willReturn($insertStmt);

$insertStmt->expects($this->once())
->method('execute')
->with($expectedParams)
->willReturn(true);
}

$handler = new MySQLHandler($pdo, $table, $additionalFields, $initialize, $level, $bubble);
$this->assertEquals($expectedResult, $handler->handle($record));
}

public function provideFakeData(): array
{
$faker = Factory::create();
$faker->seed('1234');
// Monolog 3.2.0 contains enum Level class while old static method deprecated
$loggerLevels = class_exists(\Monolog\Level::class) ? \Monolog\Level::VALUES : \Monolog\Logger::getLevels();
$data = [];

while (count($data) < 50) {
$initialColumns = $faker->unique()->words(5);
$additionalFields = $faker->unique()->words(5);
$table = $faker->unique()->word();
$time = $faker->unixTime();
$channel = $faker->unique()->word();
$level = $faker->randomElement($loggerLevels);
$msg = $faker->text;
$context = array_reduce(
$faker->randomElements(array_merge($faker->unique()->words(5), $additionalFields), 3),
static function (array $carry, string $f) use ($faker) {
$carry[$f] = $faker->word();
return $carry;
},
['id' => 'foobar'],
);
$extra = array_reduce(
$faker->randomElements(array_merge($faker->unique()->words(5), $additionalFields), 3),
static function (array $carry, string $f) use ($faker) {
$carry[$f] = $faker->word();
return $carry;
},
['id' => 'foobaz']
);

$record = new LogRecord(
\DateTimeImmutable::createFromFormat('U', strval($time)),
$channel,
\Monolog\Logger::toMonologLevel($level),
$msg,
$context,
$extra
);

$params = array_merge(
[
'channel' => $channel,
'level' => $level,
'message' => $msg,
'time' => strval($time),
],
array_filter(
$context,
static function ($v, $k) use ($additionalFields) {
return in_array($k, $additionalFields);
},
ARRAY_FILTER_USE_BOTH
),
array_filter(
$extra,
static function ($v, $k) use ($additionalFields) {
return in_array($k, $additionalFields);
},
ARRAY_FILTER_USE_BOTH
),
);

if (array_key_exists('id', $params)) {
unset($params['id']);
}

$insertSql = sprintf(
'INSERT INTO `%s` (%s) VALUES (%s);',
$table,
implode(', ', array_keys($params)),
':' . implode(', :', array_keys($params))
);

$data[] = [
$insertSql,
$params,
$initialColumns,
$record,
$table,
$additionalFields, // additional fields
$faker->boolean(), // initialize,
$faker->randomElement($loggerLevels), // level
$faker->boolean(), // bubble,
];
}

return $data;
}
}
Loading