Skip to content

Commit b1b82ae

Browse files
committed
Type safe!
1 parent 9cfca62 commit b1b82ae

File tree

11 files changed

+109
-46
lines changed

11 files changed

+109
-46
lines changed

bin/etl.php

+24-15
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
namespace AdsWarehouse;
44

55
use AdsWarehouse\Account\Account;
6-
use AdsWarehouse\ETL\AdWords;
7-
use AdsWarehouse\ETL\ETL;
8-
use AdsWarehouse\ETL\FacebookAds;
6+
use AdsWarehouse\ETL\{AdWords, ETL, FacebookAds};
97
use FacebookAds\Api;
108
use FacebookAds\Logger\CurlLogger;
119
use Google_Client;
@@ -14,35 +12,46 @@
1412
use Monolog\Handler\ErrorLogHandler;
1513
use PDO;
1614
use Siler\Monolog as Log;
15+
use function Siler\Env\{env_bool, env_var};
1716

18-
date_default_timezone_set(getenv('TZ'));
17+
require_once __DIR__ . '/../vendor/autoload.php';
1918

20-
$basedir = dirname(__DIR__, 1);
21-
require_once "$basedir/vendor/autoload.php";
19+
date_default_timezone_set(env_var('TZ'));
2220

2321
Log\handler(new ErrorLogHandler());
2422

25-
$pdo = new PDO(getenv('POSTGRES_DSN'));
23+
$pdo = new PDO(env_var('POSTGRES_DSN'));
2624
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::ERRMODE_EXCEPTION);
2725
$warehouse = new Warehouse\Pdo($pdo);
2826

2927
$google_client = new Google_Client();
3028
$google_client->setApplicationName('Ads Warehouse');
31-
$google_client->setAuthConfig($basedir . DIRECTORY_SEPARATOR . getenv('GOOGLE_CREDENTIALS'));
29+
$google_client->setAuthConfig(__DIR__ . '/../' . env_var('GOOGLE_CREDENTIALS'));
3230
$google_client->setScopes([Google_Service_Analytics::ANALYTICS_READONLY]);
3331

3432
$analytics = new Google_Service_AnalyticsReporting($google_client);
35-
$api = Api::init(getenv('FB_APP_ID'), getenv('FB_APP_SECRET'), getenv('FB_ACCESS_TOKEN'));
33+
$api = Api::init(env_var('FB_APP_ID'), env_var('FB_APP_SECRET'), env_var('FB_ACCESS_TOKEN'));
3634

37-
if (getenv('APP_DEBUG') === 'true') {
35+
if (env_bool('APP_DEBUG')) {
3836
$api->setLogger(new CurlLogger());
3937
}
4038

4139
$accounts = $warehouse->accounts();
4240

43-
$etl = array_map(fn(Account $account) => [
44-
new AdWords($warehouse, $analytics, $account->gaViewId),
45-
new FacebookAds($warehouse, $api, $account->fbAdAccountId),
46-
], $accounts);
41+
/** @var array<int, ETL> $etl */
42+
$etl = array_map(function (Account $account) use ($warehouse, $analytics, $warehouse, $api): array {
43+
/** @var array<int, ETL> $pipeline */
44+
$pipeline = [];
4745

48-
array_walk_recursive($elt, fn(ETL $etl): void => $etl->load());
46+
if ($account->gaViewId !== null) {
47+
$pipeline[] = new AdWords($warehouse, $analytics, $account->gaViewId);
48+
}
49+
50+
if ($account->fbAdAccountId !== null) {
51+
$pipeline[] = new FacebookAds($warehouse, $api, $account->fbAdAccountId);
52+
}
53+
54+
return $pipeline;
55+
}, $accounts);
56+
57+
array_walk_recursive($elt, fn(ETL $etl) => $etl->load());

bin/server.php

+8-5
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,25 @@
33
namespace AdsWarehouse;
44

55
use AdsWarehouse\Http\Handler;
6+
use GraphQL\Type\Schema;
67
use Monolog\Handler\ErrorLogHandler;
78
use PDO;
89
use Siler\Monolog as Log;
10+
use function Siler\Env\env_bool;
11+
use function Siler\Env\env_var;
912
use function Siler\Swoole\http;
1013

11-
$basedir = dirname(__DIR__, 1);
12-
require_once "$basedir/vendor/autoload.php";
14+
require_once __DIR__ . '/../vendor/autoload.php';
1315

1416
Log\handler(new ErrorLogHandler());
1517

16-
$pdo = new PDO(getenv('POSTGRES_DSN'));
18+
$pdo = new PDO(env_var('POSTGRES_DSN'));
1719
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::ERRMODE_EXCEPTION);
1820

1921
$context = new Context();
20-
$context->schema = require_once "$basedir/src/schema.php";
22+
/** @var Schema schema */
23+
$context->schema = require_once __DIR__ . '/../src/schema.php';
2124
$context->warehouse = new Warehouse\Pdo($pdo);
22-
$context->debug = getenv('APP_DEBUG') === 'true';
25+
$context->debug = env_bool('APP_DEBUG');
2326

2427
http(new Handler($context), 8000)->start();

composer.lock

+8-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

db/migrations/V1__Initial_version.sql

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
create table account
22
(
3-
id uuid not null primary key,
3+
id uuid not null primary key,
44
name varchar not null,
55
ga_view_id int,
66
fb_ad_account_id int,
@@ -9,8 +9,8 @@ create table account
99

1010
create table ad
1111
(
12-
id uuid not null primary key,
13-
account_id uuid not null references account (id),
12+
id uuid not null primary key,
13+
account_id uuid not null references account (id),
1414
name varchar not null,
1515
cost float not null default 0,
1616
impressions int not null default 0,
@@ -22,3 +22,13 @@ create table ad
2222
date date not null,
2323
timestamp timestamp not null default current_timestamp
2424
);
25+
26+
create table metric
27+
(
28+
id uuid not null primary key,
29+
account_id uuid not null references account (id),
30+
key varchar not null,
31+
value int not null default 0,
32+
source varchar not null,
33+
timestamp timestamp not null default current_timestamp
34+
);

phpcs.xml

+1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
<rule ref="PSR12">
1111
<exclude name="PSR12.Files.FileHeader"/>
1212
<exclude name="PSR12.Files.OpenTag"/>
13+
<exclude name="Generic.Files.LineLength"/>
1314
</rule>
1415
</ruleset>

psalm.xml

+8
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,12 @@
1717
<issueHandlers>
1818
<PropertyNotSetInConstructor errorLevel="suppress"/>
1919
</issueHandlers>
20+
21+
<stubs>
22+
<file name="vendor/swoole/ide-helper/output/swoole/namespace/Server.php"/>
23+
<file name="vendor/swoole/ide-helper/output/swoole/namespace/Server/Port.php"/>
24+
<file name="vendor/swoole/ide-helper/output/swoole/namespace/Http/Server.php"/>
25+
<file name="vendor/swoole/ide-helper/output/swoole/namespace/Http/Request.php"/>
26+
<file name="vendor/swoole/ide-helper/output/swoole/namespace/Http/Response.php"/>
27+
</stubs>
2028
</psalm>

src/Ad/Ad.php

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace AdsWarehouse\Ad;
44

5+
use AdsWarehouse\Account\Account;
56
use DateTime;
67
use Ramsey\Uuid\Uuid;
78
use Ramsey\Uuid\UuidInterface;
@@ -10,6 +11,7 @@ class Ad
1011
{
1112
/** @var UuidInterface|string */
1213
public $id;
14+
public Account $account;
1315
public string $name;
1416
public float $cost;
1517
public int $impressions;

src/ETL/FacebookAds.php

+11-5
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
use AdsWarehouse\Ad\Ad;
66
use AdsWarehouse\Warehouse\Warehouse;
77
use FacebookAds\Api;
8-
use FacebookAds\Object\AbstractObject;
8+
use FacebookAds\Cursor;
99
use FacebookAds\Object\AdAccount;
1010
use FacebookAds\Object\AdsInsights;
1111
use FacebookAds\Object\Values\AdsInsightsDatePresetValues;
1212
use Ramsey\Uuid\Uuid;
1313
use function AdsWarehouse\yesterday;
1414

1515
/**
16-
* @template-extends ETL<array<int, AbstractObject>>
16+
* @template-extends ETL<array<int, AdsInsights>>
1717
*/
1818
class FacebookAds extends ETL
1919
{
@@ -30,7 +30,7 @@ public function __construct(Warehouse $warehouse, Api $api, int $adAccountId)
3030
}
3131

3232
/**
33-
* @return array<int, AbstractObject>
33+
* @return array<int, AdsInsights>
3434
*/
3535
protected function extract(): array
3636
{
@@ -49,8 +49,13 @@ protected function extract(): array
4949
'date_preset' => AdsInsightsDatePresetValues::YESTERDAY,
5050
];
5151

52-
$ad_account = new AdAccount($this->adAccountId);
53-
return $ad_account->getInsights($fields, $params)->getArrayCopy();
52+
$ad_account = new AdAccount(strval($this->adAccountId));
53+
54+
/** @var Cursor $insights */
55+
$insights = $ad_account->getInsights($fields, $params);
56+
57+
/** @var array<int, AdsInsights> */
58+
return $insights->getArrayCopy();
5459
}
5560

5661
/**
@@ -60,6 +65,7 @@ protected function extract(): array
6065
protected function transform($data): array
6166
{
6267
return array_map(function (AdsInsights $insights): Ad {
68+
/** @var array<string, string> $data */
6369
$data = $insights->getData();
6470

6571
$ad = new Ad();

src/Http/Handler.php

+15-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Swoole\Http\Request;
1010
use Swoole\Http\Response;
1111
use Throwable;
12+
use UnexpectedValueException;
1213
use function Siler\Encoder\Json\decode;
1314
use function Siler\GraphQL\debug;
1415
use function Siler\GraphQL\execute;
@@ -26,10 +27,22 @@ public function __construct(Context $context)
2627
debug($context->debug ? Debug::INCLUDE_DEBUG_MESSAGE : 0);
2728
}
2829

29-
public function __invoke(Request $request, Response $response)
30+
/**
31+
* @param Request $request
32+
* @param Response $response
33+
* @throws Throwable
34+
*/
35+
public function __invoke(Request $request, Response $response): void
3036
{
3137
try {
32-
$input = decode(raw());
38+
$raw = raw();
39+
40+
if ($raw === null) {
41+
throw new UnexpectedValueException('Empty content');
42+
}
43+
44+
/** @var array<string, mixed> $input */
45+
$input = decode($raw);
3346
$result = execute($this->context->schema, $input, $this->context->rootValue, $this->context);
3447
} catch (Throwable $exception) {
3548
Log\error('Internal error', ['exception' => $exception]);

src/Warehouse/Pdo.php

+14-4
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ public function __construct(\PDO $pdo)
1919
public function store(array $ads): void
2020
{
2121
$stmt = $this->pdo->prepare(
22-
'insert into ad (id, name, cost, impressions, clicks, cpm, cpc, ctr, source, date)
23-
values (:id, :name, :cost, :impressions, :clicks, :cpm, :cpc, :ctr, :source, :date)'
22+
'insert into ad (id, account_id, name, cost, impressions, clicks, cpm, cpc, ctr, source, date)
23+
values (:id, :account_id, :name, :cost, :impressions, :clicks, :cpm, :cpc, :ctr, :source, :date)'
2424
);
2525

2626
foreach ($ads as $ad) {
2727
$stmt->bindValue('id', $ad->id);
28+
$stmt->bindValue('account_id', $ad->account->id);
2829
$stmt->bindValue('name', $ad->name);
2930
$stmt->bindValue('cost', $ad->cost);
3031
$stmt->bindValue('impressions', $ad->impressions, \PDO::PARAM_INT);
@@ -33,7 +34,7 @@ public function store(array $ads): void
3334
$stmt->bindValue('cpc', $ad->cpc);
3435
$stmt->bindValue('ctr', $ad->ctr);
3536
$stmt->bindValue('source', $ad->source);
36-
$stmt->bindValue('date', $ad->date->format('Y-m-d'));
37+
$stmt->bindValue('date', $ad->date instanceof DateTime ? $ad->date->format('Y-m-d') : $ad->date); // Type-safety!!!
3738

3839
$stmt->execute();
3940
}
@@ -44,6 +45,7 @@ public function store(array $ads): void
4445
*/
4546
public function items(): array
4647
{
48+
/** @var array<int, Ad>|false $results */
4749
$results = $this->pdo->query('select * from ad order by timestamp desc')
4850
->fetchAll(\PDO::FETCH_CLASS, Ad::class);
4951

@@ -64,6 +66,14 @@ public function drop(string $source, DateTime $date): void
6466
*/
6567
public function accounts(): array
6668
{
67-
return $this->pdo->query('select * from account')->fetchAll(\PDO::FETCH_CLASS, Account::class);
69+
/** @var array<int, Account>|false $results */
70+
$results = $this->pdo->query('select * from account')
71+
->fetchAll(\PDO::FETCH_CLASS, Account::class);
72+
73+
if ($results === false) {
74+
return [];
75+
}
76+
77+
return $results;
6878
}
6979
}

src/schema.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
namespace AdsWarehouse;
44

5-
use function Siler\File\concat_files;
6-
use function Siler\File\recur_iter_dir;
5+
use function Siler\File\{concat_files, recur_iter_dir};
76
use function Siler\GraphQL\schema;
87

9-
$basedir = dirname(__DIR__, 1);
10-
$type_defs = concat_files(recur_iter_dir("$basedir/src", '/\.graphql$/'));
11-
$resolvers = require_once "$basedir/src/resolvers.php";
8+
$type_defs = concat_files(recur_iter_dir(__DIR__ . '/../src', '/\.graphql$/'));
9+
10+
/** @var array<string, mixed> $resolvers */
11+
$resolvers = require_once __DIR__ . '/../src/resolvers.php';
1212

1313
return schema($type_defs, $resolvers);

0 commit comments

Comments
 (0)