Skip to content
This repository was archived by the owner on Apr 1, 2024. It is now read-only.

Commit c6afdb5

Browse files
committed
remove individual trackers for simplicity
1 parent c723df9 commit c6afdb5

17 files changed

Lines changed: 129 additions & 453 deletions

README.md

Lines changed: 10 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
# Statistics
1313

14-
This package enables a Laravel application to maintain statistics of aggregated database records. It serves as a companion package to (and relies upon) [triggers](https://github.com/mattkingshott/triggers).
14+
This package enables a Laravel application to maintain aggregated statistics for database tables. It serves as a companion package to (and relies upon) [triggers](https://github.com/mattkingshott/triggers).
1515

1616
## Who is this for?
1717

@@ -53,30 +53,6 @@ $stats = Statistic::query()
5353
| Projects | `{ "count" : 3 }` |
5454
| Tasks | `{ "count" : 2 }` |
5555

56-
This becomes even more powerful when using joins for individual rows:
57-
58-
```php
59-
$users = User::query()
60-
->join('statistics', function ($join) {
61-
$join->on('users.id', '=', 'statistics.id')
62-
->where('statistics.table', 'users');
63-
})
64-
->orderByRaw('`values`->"$.post_count" DESC')
65-
->get(['users.name', DB::raw('`values`->"$.post_count" AS `posts`')])
66-
```
67-
68-
| Name | Posts |
69-
| ----- | ----- |
70-
| John | 6 |
71-
| Fred | 4 |
72-
| Dave | 1 |
73-
74-
## How does it work?
75-
76-
The package will automatically register and migrate a `statistics` table to your database. This table then serves as a repository for aggregated values. You can then easily join records to this table to get the associated metrics you need.
77-
78-
The aggregated values are maintained using database triggers, which will automatically fire after a record is inserted, updated or deleted.
79-
8056
## Installation
8157

8258
Pull in the package using Composer:
@@ -95,54 +71,34 @@ php artisan vendor:publish
9571

9672
## Usage
9773

98-
The package allows you to:
74+
The package will automatically register and migrate a `statistics` table to your database. This table then serves as a repository for aggregated values. The values are maintained using database triggers, which will automatically fire after a record is inserted, updated or deleted.
9975

100-
1. Maintain statistics for all the records in a table.
101-
2. Maintain statistics for individual rows in a table.
102-
3. Maintain statistics for individual AND all records in a table.
103-
104-
Before proceeding further, it is important to remember that database triggers (which the package relies on) can only be added to a table after it has been created. Therefore, you should ensure that any referenced tables have first been added via `Schema::create` within your migrations e.g.
105-
106-
```php
107-
// At this point, adding statistics that rely on the 'users' table will
108-
// throw a SQL error as the table has not yet been added to the database.
109-
110-
Schema::create('users', function(Blueprint $table) {
111-
// ...
112-
});
113-
114-
// At this point, adding statistics that rely on the 'users' table will not
115-
// throw a SQL error as the table has been added to the database.
116-
```
117-
118-
### Configuring models
76+
> Before proceeding further, it is important to remember that database triggers (which the package relies on) can only be added to a table after it has been created. In other words, don't try to create statistics for a table before it has been created by `Schema::create` (this will become clearer in the examples below).
11977
12078
To begin, add the `InteractsWithStatistics` trait to any `Model` class that you want to maintain statistics for e.g.
12179

12280
```php
12381
namespace App\Models;
12482

12583
use Illuminate\Database\Eloquent\Model;
126-
use Statistics\Traits\InteractsWithStatistics;
84+
use Statistics\InteractsWithStatistics;
12785

12886
class Article extends Model
12987
{
13088
use InteractsWithStatistics;
13189
}
13290
```
13391

134-
### Tracking all records
135-
136-
To maintain statistics across the entire table (e.g. how many rows there are), call the static `trackAll` method on the `Model`.
92+
Next, call the static `track` method on the `Model`.
13793

13894
```php
139-
Article::trackAll();
95+
Article::track();
14096
```
14197

14298
Next, call one or more of the available aggregation methods:
14399

144100
```php
145-
Article::trackAll()
101+
Article::track()
146102
->count() // Count all records
147103
->sum('likes') // Get the sum of all records using the 'likes' column
148104
->average('likes') // Get the average value from the 'likes' column
@@ -153,7 +109,7 @@ Article::trackAll()
153109
You can call an aggregation method more than once if you need to maintain statistics on multiple columns. Simply supply a custom name to differentiate them:
154110

155111
```php
156-
Article::trackAll()
112+
Article::track()
157113
->count()
158114
->sum('likes', 'sum_likes')
159115
->sum('views', 'sum_views');
@@ -162,7 +118,7 @@ Article::trackAll()
162118
Finally, call the `create` method to install the triggers.
163119

164120
```php
165-
Article::trackAll()
121+
Article::track()
166122
->count()
167123
->create();
168124
```
@@ -181,116 +137,13 @@ class CreateArticlesTable extends Migration
181137
$table->string('title');
182138
});
183139

184-
Article::trackAll()
140+
Article::track()
185141
->count()
186142
->create();
187143
}
188144
}
189145
```
190146

191-
### Tracking individual rows
192-
193-
In addition to maintaining statistics on a whole table, you can also maintain statistics for individual rows. This can be useful when tracking related records e.g. the count of 'articles', 'projects' and 'tasks' belonging to a 'user'.
194-
195-
To begin, call the static `track` method on the `Model`:
196-
197-
```php
198-
User::track();
199-
```
200-
201-
Next, we need to add a watcher for each of the related tables. We do this by calling the `watch` method and supplying the related `Model`:
202-
203-
```php
204-
User::track()
205-
->watch(Task::class);
206-
```
207-
208-
The package will guess the foreign key on `Task` by combining the main model (`User`) and `_id`. However, you can override this by supplying your own foreign key:
209-
210-
```php
211-
User::track()
212-
->watch(Task::class, 'author_id');
213-
```
214-
215-
Next, we need to provide an array of SQL statements that the trigger should execute in order to maintain one or more statistics:
216-
217-
```php
218-
User::track()
219-
->watch(Task::class, [
220-
'task_count' => "(SELECT COUNT(*) FROM `tasks` WHERE `user_id` = {ROW}.user_id)",
221-
]);
222-
223-
// Or when supplying a custom foreign key...
224-
225-
User::track()
226-
->watch(Task::class, 'author_id', [
227-
'task_count' => "(SELECT COUNT(*) FROM `tasks` WHERE `author_id` = {ROW}.author_id)",
228-
]);
229-
```
230-
231-
Notice the use of `{ROW}` in the SQL statement. You can use this placeholder to access the current row within the trigger. `{ROW}` will be automatically replaced with `OLD` or `NEW` depending on the event type.
232-
233-
You are free to maintain more than one statistic for a related table if required e.g.
234-
235-
```php
236-
User::track()
237-
->watch(Task::class, [
238-
'task_count' => "(SELECT COUNT(*) FROM `tasks` WHERE `user_id` = {ROW}.user_id)",
239-
'task_max_priority' => "(SELECT MAX(`priority`) FROM `tasks` WHERE `user_id` = {ROW}.user_id)",
240-
]);
241-
```
242-
243-
Next, repeat the process for any further watchers that you need e.g.
244-
245-
```php
246-
User::track()
247-
->watch(Task::class, [
248-
'task_count' => "(SELECT COUNT(*) FROM `tasks` WHERE `user_id` = {ROW}.user_id)",
249-
'task_max_priority' => "(SELECT MAX(`priority`) FROM `tasks` WHERE `user_id` = {ROW}.user_id)",
250-
])
251-
->watch(Project::class, [
252-
'project_count' => "(SELECT COUNT(*) FROM `projects` WHERE `user_id` = {ROW}.user_id)",
253-
])
254-
->watch(Article::class, [
255-
'article_count' => "(SELECT COUNT(*) FROM `articles` WHERE `user_id` = {ROW}.user_id)",
256-
]);
257-
```
258-
259-
Finally, call the `create` method to install the triggers.
260-
261-
```php
262-
User::track()
263-
->watch(Task::class, [
264-
'task_count' => "(SELECT COUNT(*) FROM `tasks` WHERE `user_id` = {ROW}.user_id)",
265-
])
266-
->create();
267-
```
268-
269-
#### Example
270-
271-
Here's a simple example within a database migration:
272-
273-
```php
274-
return new class extends Migration
275-
{
276-
public function up() : void
277-
{
278-
User::track()
279-
->watch(Task::class, [
280-
'task_count' => "(SELECT COUNT(*) FROM `tasks` WHERE `user_id` = {ROW}.user_id)",
281-
'task_max_priority' => "(SELECT MAX(`priority`) FROM `tasks` WHERE `user_id` = {ROW}.user_id)",
282-
])
283-
->watch(Project::class, [
284-
'project_count' => "(SELECT COUNT(*) FROM `projects` WHERE `user_id` = {ROW}.user_id)",
285-
])
286-
->watch(Article::class, [
287-
'article_count' => "(SELECT COUNT(*) FROM `articles` WHERE `user_id` = {ROW}.user_id)",
288-
])
289-
->create();
290-
}
291-
};
292-
```
293-
294147
## Contributing
295148

296149
Thank you for considering a contribution to the package. You are welcome to submit a PR containing improvements, however if they are substantial in nature, please also be sure to include a test or tests.

database/migrations/2014_10_12_000000_create_statistics_table.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,8 @@
1212
public function up() : void
1313
{
1414
Schema::create(config('statistics.table', 'statistics'), function(Blueprint $table) {
15-
$table->string('table', 100);
16-
$table->string('id')->default('');
15+
$table->string('table', 100)->primary();
1716
$table->json('values');
18-
19-
$table->unique(['table', 'id']);
2017
});
2118
}
2219

resources/version.svg

Lines changed: 2 additions & 2 deletions
Loading

src/InteractsWithStatistics.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Statistics;
4+
5+
trait InteractsWithStatistics
6+
{
7+
/**
8+
* Track all model records.
9+
*
10+
*/
11+
public static function track() : Tracker
12+
{
13+
return Tracker::make((new static())->getTable());
14+
}
15+
}

src/Models/Statistic.php

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/Statistic.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Statistics;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
class Statistic extends Model
8+
{
9+
protected $guarded = [];
10+
11+
protected $casts = ['values' => 'array'];
12+
13+
public $timestamps = false;
14+
15+
/**
16+
* Get the value indicating whether the IDs are incrementing.
17+
*
18+
*/
19+
public function getIncrementing() : bool
20+
{
21+
return false;
22+
}
23+
24+
/**
25+
* Get the auto-incrementing key type.
26+
*
27+
*/
28+
public function getKeyType() : string
29+
{
30+
return 'string';
31+
}
32+
33+
/**
34+
* Get the table associated with the model.
35+
*
36+
*/
37+
public function getTable() : string
38+
{
39+
return config('statistics.table', 'statistics');
40+
}
41+
}
Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<?php declare(strict_types=1);
22

3-
namespace Statistics\Trackers;
3+
namespace Statistics;
44

55
use Triggers\Trigger;
66
use Illuminate\Support\Collection;
77
use Illuminate\Support\Facades\File;
88

9-
class TableTracker
9+
class Tracker
1010
{
1111
/**
1212
* The list of aggregates to track.
@@ -78,12 +78,17 @@ public function create() : void
7878
$statement = str_replace(
7979
array_keys($placeholders),
8080
array_values($placeholders),
81-
File::get(__DIR__ . '/../../stubs/table.stub')
81+
File::get(__DIR__ . '/../stubs/trigger.stub')
8282
);
8383

84-
Trigger::table($this->table)->key('all')->afterDelete(fn() => $statement);
85-
Trigger::table($this->table)->key('all')->afterInsert(fn() => $statement);
86-
Trigger::table($this->table)->key('all')->afterUpdate(fn() => $statement);
84+
Statistic::create([
85+
'table' => $this->table,
86+
'values' => $this->aggregates->mapWithKeys(fn($item) => [$item['key'] => 0])->toArray(),
87+
]);
88+
89+
Trigger::table($this->table)->key('statistics')->afterDelete(fn() => $statement);
90+
Trigger::table($this->table)->key('statistics')->afterInsert(fn() => $statement);
91+
Trigger::table($this->table)->key('statistics')->afterUpdate(fn() => $statement);
8792
}
8893

8994
/**

0 commit comments

Comments
 (0)