Compare commits
9
Commits
81b978b13a
...
22c6dbc6d8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22c6dbc6d8 | ||
|
|
184d0cb9c4 | ||
|
|
ec76c2d640 | ||
|
|
b45f5de151 | ||
|
|
a94b55ded2 | ||
|
|
dab4243872 | ||
|
|
a6e0f56b2e | ||
|
|
62620360e7 | ||
|
|
d10acef42a |
@@ -1,3 +1,9 @@
|
||||
## 2.5.0 [2026-02-10]
|
||||
|
||||
### Features
|
||||
1. Replaced the Eloquent HasEvents trait with a minimal custom implementation
|
||||
2. Added query logging to the Builder class
|
||||
|
||||
## 2.3.0 [2025-04-21]
|
||||
|
||||
### Features
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"smi2/phpclickhouse": "^1.4.2",
|
||||
"glushkovds/clickhouse-builder": "^6.1.2",
|
||||
"glushkovds/clickhouse-builder": "^7",
|
||||
"illuminate/support": ">=7",
|
||||
"illuminate/database": ">=7",
|
||||
"glushkovds/php-clickhouse-schema-builder": "^1.0.2"
|
||||
|
||||
+2
-10
@@ -7,9 +7,8 @@ namespace PhpClickHouseLaravel;
|
||||
use ClickHouseDB\Client;
|
||||
use ClickHouseDB\Statement;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasAttributes;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasEvents;
|
||||
use PhpClickHouseLaravel\Concerns\HasEvents;
|
||||
use Illuminate\Database\Eloquent\Concerns\HidesAttributes;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -59,13 +58,6 @@ class BaseModel
|
||||
*/
|
||||
public $wasRecentlyCreated = false;
|
||||
|
||||
/**
|
||||
* The event dispatcher instance.
|
||||
*
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected static $dispatcher;
|
||||
|
||||
/**
|
||||
* The name of the database connection to use.
|
||||
*
|
||||
@@ -327,7 +319,7 @@ class BaseModel
|
||||
|
||||
protected function newQuery(): Builder
|
||||
{
|
||||
return new Builder($this->getThisClient());
|
||||
return new Builder($this->getThisClient(), $this->connection);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+23
-2
@@ -31,10 +31,13 @@ class Builder extends BaseBuilder
|
||||
*/
|
||||
protected $connection = Connection::DEFAULT_NAME;
|
||||
|
||||
public function __construct(?Client $client = null)
|
||||
public function __construct(?Client $client = null, ?string $connection = null)
|
||||
{
|
||||
$this->grammar = new Grammar();
|
||||
$this->client = $client ?? $this->getThisClient();
|
||||
if ($connection !== null) {
|
||||
$this->connection = $connection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,12 +57,30 @@ class Builder extends BaseBuilder
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the elapsed time in milliseconds since a given starting point.
|
||||
*
|
||||
* @param float $start
|
||||
* @return float
|
||||
*/
|
||||
protected function getElapsedTime($start)
|
||||
{
|
||||
return round((microtime(true) - $start) * 1000, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Statement
|
||||
*/
|
||||
public function get(array $bindings = []): Statement
|
||||
{
|
||||
return $this->client->select($this->toSql(), $bindings);
|
||||
$query = $this->toSql();
|
||||
$start = microtime(true);
|
||||
|
||||
$statement = $this->client->select($query, $bindings);
|
||||
|
||||
$this->resolveConnection()->logQuery($query, $bindings, $this->getElapsedTime($start));
|
||||
|
||||
return $statement;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -220,7 +220,7 @@ trait BuilderMethodsFromLaravel
|
||||
*/
|
||||
public function aggregate($function, $columns = ['*'])
|
||||
{
|
||||
$results = $this->cloneWithout($this->unions || $this->havings ? [] : ['columns'])
|
||||
$results = $this->cloneWithout($this->unions || $this->havings ? [] : ['columns' => []])
|
||||
->setAggregate($function, $columns)
|
||||
->getRows();
|
||||
|
||||
@@ -249,4 +249,4 @@ trait BuilderMethodsFromLaravel
|
||||
$table = $this->tableSources ?? (string)$this->getFrom()->getTable();
|
||||
$this->client->insertAssocBulk($table, $values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpClickHouseLaravel\Concerns;
|
||||
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Events\NullDispatcher;
|
||||
|
||||
/**
|
||||
* Minimal event dispatching for ClickHouse models.
|
||||
*
|
||||
* Unlike Eloquent's HasEvents trait, this does not require extending
|
||||
* Illuminate\Database\Eloquent\Model, making it compatible with
|
||||
* ClickHouse's BaseModel, which is not an Eloquent model.
|
||||
*
|
||||
* Supports: creating, created, saved events (the events BaseModel actually fires).
|
||||
* Does not include: observers, $dispatchesEvents mapping, bootHasEvents, or
|
||||
* attribute-based observer registration — these are Eloquent-specific features.
|
||||
*/
|
||||
trait HasEvents
|
||||
{
|
||||
/**
|
||||
* @var Dispatcher|null
|
||||
*/
|
||||
protected static $dispatcher;
|
||||
|
||||
/**
|
||||
* @param string $event
|
||||
* @param bool $halt
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function fireModelEvent(string $event, bool $halt = true): mixed
|
||||
{
|
||||
if (!isset(static::$dispatcher)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$method = $halt ? 'until' : 'dispatch';
|
||||
|
||||
return static::$dispatcher->{$method}(
|
||||
"eloquent.{$event}: " . static::class,
|
||||
$this
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Dispatcher|null
|
||||
*/
|
||||
public static function getEventDispatcher(): ?Dispatcher
|
||||
{
|
||||
return static::$dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Dispatcher $dispatcher
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setEventDispatcher(Dispatcher $dispatcher): void
|
||||
{
|
||||
static::$dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function unsetEventDispatcher(): void
|
||||
{
|
||||
static::$dispatcher = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callback
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function withoutEvents(callable $callback): mixed
|
||||
{
|
||||
$dispatcher = static::getEventDispatcher();
|
||||
|
||||
if ($dispatcher) {
|
||||
static::setEventDispatcher(new NullDispatcher($dispatcher));
|
||||
}
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
if ($dispatcher) {
|
||||
static::setEventDispatcher($dispatcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -66,7 +66,7 @@ class Connection extends BaseConnection
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function select($query, $bindings = [], $useReadPdo = true): array
|
||||
public function select($query, $bindings = [], $useReadPdo = true, array $fetchUsing = []): array
|
||||
{
|
||||
$query = QueryGrammar::prepareParameters($query);
|
||||
return $this->run($query, $bindings, function ($query, $bindings) {
|
||||
|
||||
@@ -15,7 +15,7 @@ class CurlerRollingWithRetries extends CurlerRolling
|
||||
protected $retries = 0;
|
||||
|
||||
/** @inheritDoc */
|
||||
public function execOne(CurlerRequest $request, $auto_close = false)
|
||||
public function execOne(CurlerRequest $request, bool $auto_close = false): int
|
||||
{
|
||||
$attempts = 1 + max(0, $this->retries);
|
||||
$httpCode = 0;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
|
||||
use PhpClickHouseLaravel\BaseModel;
|
||||
|
||||
class EventsTestModel extends BaseModel
|
||||
{
|
||||
protected $table = 'events_test';
|
||||
|
||||
public function fireModelEvent(string $event, bool $halt = true): mixed
|
||||
{
|
||||
return parent::fireModelEvent($event, $halt);
|
||||
}
|
||||
}
|
||||
|
||||
class EventsTest extends PHPUnitTestCase
|
||||
{
|
||||
protected function tearDown(): void
|
||||
{
|
||||
BaseModel::unsetEventDispatcher();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testFireModelEventReturnsTrueWithoutDispatcher(): void
|
||||
{
|
||||
BaseModel::unsetEventDispatcher();
|
||||
|
||||
$model = new EventsTestModel();
|
||||
|
||||
$this->assertTrue($model->fireModelEvent('creating'));
|
||||
}
|
||||
|
||||
public function testCreatingEventCanHaltCreation(): void
|
||||
{
|
||||
$dispatcher = $this->createMock(Dispatcher::class);
|
||||
$dispatcher->method('until')->willReturn(false);
|
||||
BaseModel::setEventDispatcher($dispatcher);
|
||||
|
||||
$result = EventsTestModel::create(['field1' => 'value1']);
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testCreatedEventFires(): void
|
||||
{
|
||||
$firedEvents = [];
|
||||
BaseModel::setEventDispatcher($this->mockDispatcher($firedEvents));
|
||||
|
||||
$model = new EventsTestModel();
|
||||
$model->fireModelEvent('created', false);
|
||||
|
||||
$this->assertContains('eloquent.created: ' . EventsTestModel::class, $firedEvents);
|
||||
}
|
||||
|
||||
public function testSavedEventFires(): void
|
||||
{
|
||||
$firedEvents = [];
|
||||
BaseModel::setEventDispatcher($this->mockDispatcher($firedEvents));
|
||||
|
||||
$model = new EventsTestModel();
|
||||
$model->fireModelEvent('saved', false);
|
||||
|
||||
$this->assertContains('eloquent.saved: ' . EventsTestModel::class, $firedEvents);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $firedEvents
|
||||
*/
|
||||
private function mockDispatcher(array &$firedEvents): Dispatcher
|
||||
{
|
||||
$dispatcher = $this->createMock(Dispatcher::class);
|
||||
|
||||
$dispatcher->method('until')
|
||||
->willReturnCallback(function (string $event) use (&$firedEvents) {
|
||||
$firedEvents[] = $event;
|
||||
return true;
|
||||
});
|
||||
|
||||
$dispatcher->method('dispatch')
|
||||
->willReturnCallback(function (string $event) use (&$firedEvents) {
|
||||
$firedEvents[] = $event;
|
||||
return [true];
|
||||
});
|
||||
|
||||
return $dispatcher;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user