すでにGA4の設定をGCPの設定済みと仮定して、Laravelでの実装のみのアプトプットになります。
もし、それまでの設定が必要な場合は下記の記事を参考にしてくだしい。
https://www.fourier.jp/blog/ga4-with-php
ライブラリーをインストール
composer require google/analytics-data
これも追加
composer require google/apiclient
JSONファイルをstoragaディレクトリー配置する必要がある
(gitignore設定は必須)
しかしエラー
Class "Google\Analytics\Data\V1beta\BetaAnalyticsDataClient" not found
ライブラリーは正しく存在されていることを確認
composer show | grep google/analytics-data
google/analytics-data 0.22.0 Google Analytics Data Client for PHP
解決
パスが変わってたみたい(下記が新しい名前空間です)
use Google\Analytics\Data\V1beta\Client\BetaAnalyticsDataClient;
bcmathが必要とエラーが出たのでDockerfileに追加
RUN apt-get update && apt-get install -y \
git \
unzip \
libpq-dev \
libzip-dev \
&& docker-php-ext-install pdo pdo_mysql zip bcmath
これで使えるようになった
public function handle()
{
$propertyId = config('google.analytics.propertyId');
$credentialsPath = config('google.analytics.credentials');
$client = new BetaAnalyticsDataClient([
'credentials' => $credentialsPath,
'transport' => 'rest',
]);
$request = new RunReportRequest([
'property' => 'properties/' . $propertyId,
'date_ranges' => [
new DateRange([
'start_date' => '30daysAgo',
'end_date' => 'today',
]),
],
'dimensions' => [
new Dimension(['name' => 'pagePath']),
new Dimension(['name' => 'pageTitle']),
],
'metrics' => [
new Metric(['name' => 'screenPageViews']),
],
'limit' => 100,
]);
$request->setReturnPropertyQuota(true);// 使用制限を確認する
// dd($response->getPropertyQuota());
$response = $client->runReport($request);
foreach ($response->getRows() as $row) {
$dimensionValues = $row->getDimensionValues();
$metricValues = $row->getMetricValues();
$pagePath = $dimensionValues[0]->getValue();
$pageTitle = $dimensionValues[1]->getValue();
$pageViews = $metricValues[0]->getValue();
$this->info("Path: $pagePath | Title: $pageTitle | PV: $pageViews");
}
}
ここからcronで時間で自動的に実行されるようにする
メモ
job_batches テーブルが作成されたがこれは何?
laravel11からはroutes/console.logに記述が必要になったみたい
https://readouble.com/laravel/11.x/ja/scheduling.html
一旦下記のように記述してみたがうまくいかない
use Illuminate\Support\Facades\Schedule;
Schedule::command('analitics-command')->everyMinute() //毎分実行;
しかし、php artisan schedule:list
をして確認すると実行はされているっぽい
なぜか,ログには実行内容が記述されない
php artisan schedule:list
0 * * * * php artisan inspire ........................................................................................................................................ Next Due: 14分後
* * * * * php artisan analitics-command .............................................................................................................................. Next Due: 56秒後
下記のコマンドを実行すると処理は実行されるが根本的な問題は解決していない
php artisan schedule:run
解決
cron をインストールが必要
一旦コンテナに直でインストール
apt-get install cron
これがないとcron ファイルを操作できない
apt-get install -y nano
cron 設定ファイルを修正する必要があるため、ファイルを開く
crontab -e
Laravelのスケジュールコマンドを毎分実行する設定を追加します。(ここはパスによって変更が必要(
* * * * * cd /var/www/html && /usr/local/bin/php artisan schedule:run >> /dev/null 2>&1
cronサービスを起動
service cron start
実際に動いているか確認
service cron status
cron is running.
時間通りに処理もうまくいっている
リアルタイム(30分)のごとの値を取得する
<?php
namespace App\Console\Commands\GoogleAnalytics;
use Illuminate\Console\Command;
use Google\Analytics\Data\V1beta\Client\BetaAnalyticsDataClient;
// リアルタイム レポート関連クラス
use Google\Analytics\Data\V1beta\RunRealtimeReportRequest;
use Google\Analytics\Data\V1beta\Dimension;
use Google\Analytics\Data\V1beta\Metric;
class RealTimeReportCommand extends Command
{
/**
* The name and signature of the console command.
*/
protected $signature = 'app:real-time-report-command';
/**
* The console command description.
*/
protected $description = 'GA4のリアルタイムレポート (runRealtimeReport) でデータを取得する';
public function handle()
{
// 1) 環境変数や config などから GA4 プロパティID と 認証JSONパスを取得
$propertyId = config('google.analytics.propertyId'); // 例: 123456789
$credentialsPath = config('google.analytics.credentials'); // 例: storage_path('service-account.json')
// 2) BetaAnalyticsDataClient のインスタンスを生成
// 'credentials' にサービスアカウントJSONのパスを指定
$client = new BetaAnalyticsDataClient([
'credentials' => $credentialsPath,
]);
try {
// 3) リアルタイム レポート用リクエストを作成
// ※ "property" は "properties/123456789" の形で設定する
// ※ 取得したい dimension / metric に注意
$request = new RunRealtimeReportRequest([
'property' => "properties/{$propertyId}",
'dimensions' => [
// new Dimension(['name' => 'eventName']),
new Dimension(['name' => 'unifiedScreenName']), // 記事名
],
'metrics' => [
// new Metric(['name' => 'activeUsers']),
new Metric(['name' => 'screenPageViews']), // PV数
],
'limit' => 10,
]);
// 4) 実際に runRealtimeReport を呼び出し
$response = $client->runRealtimeReport($request);
// 5) レスポンスを表示
$this->info("---- GA4 Realtime Report ----");
foreach ($response->getRows() as $row) {
// dd($row);
$dimensions = $row->getDimensionValues();
$metrics = $row->getMetricValues();
$unifiedScreenName = $dimensions[0]->getValue() ?? '(no event)';
$screenPageViews = $metrics[0]->getValue() ?? 0;
// $activeUsers = $metrics[1]->getValue() ?? 0;
// $eventName = $metrics[2]->getValue() ?? '(no event)';
$this->info("unifiedScreenName: {$unifiedScreenName}, screenPageViews: {$screenPageViews}");
}
} catch (\Exception $e) {
$this->error("Error: " . $e->getMessage());
}
return 0;
}
}
runReportの場合
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Carbon\Carbon;
use Google\Analytics\Data\V1beta\Client\BetaAnalyticsDataClient;
use Google\Analytics\Data\V1beta\RunReportRequest;
use Google\Analytics\Data\V1beta\Filter;
use Google\Analytics\Data\V1beta\Filter\StringFilter\MatchType;
use Google\Analytics\Data\V1beta\FilterExpression;
use Google\Analytics\Data\V1beta\OrderBy;
use Google\Analytics\Data\V1beta\OrderBy\DimensionOrderBy;
use Google\Analytics\Data\V1beta\DateRange;
use Google\Analytics\Data\V1beta\Dimension;
use Google\Analytics\Data\V1beta\Metric;
use Google\Service\Dfareporting\Order;
use Illuminate\Support\Facades\DB;
class RunReport extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:run-report';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
$yesterday = Carbon::yesterday()->format('Y-m-d');
$propertyId = config('google.analytics.propertyId');
$credentialsPath = config('google.analytics.credentials');
$client = new BetaAnalyticsDataClient([
'credentials' => $credentialsPath,
'transport' => 'rest',
]);
$request = new RunReportRequest([
'property' => 'properties/' . $propertyId,
'date_ranges' => [
new DateRange([
'start_date' => $yesterday,
'end_date' => $yesterday,
]),
],
'dimensions' => [
new Dimension(['name' => 'pagePathPlusQueryString']),
],
'metrics' => [
new Metric(['name' => 'screenPageViews']),
],
'order_bys' => [
new OrderBy([
'dimension' => new DimensionOrderBy(['dimension_name' => 'pagePathPlusQueryString']),
'desc' => false,
]),
],
'limit' => 10,
]);
// $request->setReturnPropertyQuota(true);// 使用制限を確認する
$response = $client->runReport($request);
$posts = DB::connection('wordpress')->table('wp_posts')->where('post_type', 'post')->where('post_status', 'publish')->pluck('ID')->toArray();
$pv_Array = [];
// 存在する記事のIDのみを保存する
foreach ($response->getRows() as $row) {
$dimensionValues = $row->getDimensionValues();
$metricValues = $row->getMetricValues();
$pagePathPlusQueryString = $dimensionValues[0]->getValue();
$pageViews = $metricValues[0]->getValue();
$postId = $this->getPostIdFromPagePath($pagePathPlusQueryString, $posts);
if ($postId) {
$pv_Array[] = [
'post_id' => $postId,
'pv_count' => $pageViews,
'target_date' => $yesterday,
'created_by' => '日時記事アクセス集計バッチ',
'updated_by' => '日時記事アクセス集計バッチ',
'created_at' => now(),
'updated_at' => now(),
];
}
// エピソードに存在するwordpressの記事のIDのみPVを保存する
\Log::info("post_id: $postId | pagePathPlusQueryString: $pagePathPlusQueryString | PV: $pageViews");
$this->info("post_id: $postId | pagePathPlusQueryString: $pagePathPlusQueryString | PV: $pageViews");
}
// if (!empty($pv_Array)) {
// DB::table('posts_pv')->insert($pv_Array);
// }
}
/**
* pagePathPlusQueryStringからpost_idを取得する関数
*/
function getPostIdFromPagePath($pagePathPlusQueryString, $posts):int | null
{
preg_match('/\/\?p=(\d+)$/', $pagePathPlusQueryString, $matches);
// dd($matches, $posts, gettype($posts));
// 記事IDが存在する場合はそのIDを返す
if (!empty($matches[1]) && in_array($matches[1], $posts)) {
return $matches[1];
}
return null;
}
}
Dockerコンテナ間通信方法
1 共有ネットワークを作成する
docker network create -d bridge local-cms-network
2 共通ネットワークが作成されたか確認
docker inspect local-cms-network
3 docker-compose.ymlに共通ネットワークを記載(両方のymlに記載)
# それぞれのサービスに記載
networks:
- cms-network
# 最後に記載
networks:
cms-network:
external: true
name: local-cms-network
4 docker再起動
docker compose up -d --build
Laravelでレスポンスボディを取得する方法
$jsonString = $response->serializeToJsonString();
dd($jsonString);
ディメンションをそれぞれ取得する方法(ページスクリーン/ブラウザ)
<?php
namespace App\Console\Commands\GoogleAnalytics;
use Illuminate\Console\Command;
use Carbon\Carbon;
use Google\Analytics\Data\V1beta\Client\BetaAnalyticsDataClient;
use Google\Analytics\Data\V1beta\RunPivotReportRequest;
use Google\Analytics\Data\V1beta\DateRange;
use Google\Analytics\Data\V1beta\Dimension;
use Google\Analytics\Data\V1beta\Metric;
use Google\Analytics\Data\V1beta\Pivot;
use Exception;
use Illuminate\Support\Facades\Log;
class RunPivotReportCommand extends Command
{
protected $signature = 'schedule:run-pivot-report';
protected $description = 'GA4でpagePathPlusQueryString×browserのピボットを取得する';
public function handle(): int
{
try {
$client = $this->createGa4Client();
$request = $this->createRunPivotReportRequest();
$request->setReturnPropertyQuota(true);// 使用制限を確認する
$response = $client->runPivotReport($request);
$jsonString = $response->serializeToJsonString();
// dd($response->getPropertyQuota());
// dd($jsonString);
// レスポンスを表示
$this->displayPivotResult($response);
} catch (Exception $e) {
Log::error($e->getMessage());
$this->error($e->getMessage());
return Command::FAILURE;
}
Log::info('正常に終了しました');
return Command::SUCCESS;
}
private function createGa4Client(): BetaAnalyticsDataClient
{
return new BetaAnalyticsDataClient([
'credentials' => config('google.analytics.credentials'),
'transport' => 'rest',
]);
}
private function createRunPivotReportRequest(): RunPivotReportRequest
{
return new RunPivotReportRequest([
'property' => 'properties/' . config('google.analytics.propertyId'),
// 計測期間
'date_ranges' => [
new DateRange([
'start_date' => '2025-02-25',
'end_date' => '2025-02-25',
])
],
// 2つのディメンション
'dimensions' => [
new Dimension(['name' => 'pagePathPlusQueryString']),
new Dimension(['name' => 'browser']),
],
// 取得したい指標
'metrics' => [
new Metric(['name' => 'activeUsers']),
],
// 2つのピボットオブジェクトを定義
// 1. pagePathPlusQueryString
// 2. browser
// これによって GA4 の「2次元ピボット」として認識されます。
'pivots' => [
new Pivot([
'field_names' => ['pagePathPlusQueryString'],
'limit' => 50,
]),
new Pivot([
'field_names' => ['browser'],
'limit' => 10,
]),
],
]);
}
private function displayPivotResult($response): void
{
// pivotHeaders は、pivots で定義した順に返る
// $pivotHeaders[0] => pagePathPlusQueryString 用のヘッダ
// $pivotHeaders[1] => browser 用のヘッダ
$pivotHeaders = $response->getPivotHeaders();
// 1つめのピボット (行方向) の dimension 値一覧
// たとえば p?=13, p?=14 など
$pagePathHeader = $pivotHeaders[0];
$pagePathValues = [];
foreach ($pagePathHeader->getPivotDimensionHeaders() as $pdh) {
// dimensionValues は通常1要素だが、複数の場合もある
$val = $pdh->getDimensionValues()[0]->getValue();
$pagePathValues[] = $val;
}
// 2つめのピボット (列方向) の dimension 値一覧
// たとえば Chrome, Firefox, Safari 等
$browserHeader = $pivotHeaders[1];
$browserValues = [];
foreach ($browserHeader->getPivotDimensionHeaders() as $pdh) {
$val = $pdh->getDimensionValues()[0]->getValue();
$browserValues[] = $val;
}
// 実際のデータ行
// pivot 2次元の場合、各 row は「pagePathの値, browserの値」と metrics がセットになって返る
// → dimensionValues[0] => 例: "p?=13"
// dimensionValues[1] => 例: "Chrome"
// metricValues => 例: activeUsers=1
// ただし GA4ピボットは「1セルごとに1行」で返るため、行×列形式にするには再構築が必要
$resultsMatrix = []; // $resultsMatrix[pagePath][browser] = activeUsers
foreach ($response->getRows() as $row) {
$dims = $row->getDimensionValues(); // [0]: pagePathPlusQueryString, [1]: browser
$mets = $row->getMetricValues(); // [0]: activeUsers
$pagePath = $dims[0]->getValue();
$browser = $dims[1]->getValue();
$activeUsers = (int)$mets[0]->getValue();
$resultsMatrix[$pagePath][$browser] = $activeUsers;
}
// 以下は画面出力用の例 (行が pagePathPlusQueryString, 列が browser)
// まずブラウザの見出し行を表示
$this->line("pagePathPlusQueryString\t" . implode("\t", $browserValues));
// 各pagePath行を表示
foreach ($pagePathValues as $pp) {
// 行の左端に pagePath
$rowOutput = [$pp];
// その列のブラウザ順に値を取り出す
foreach ($browserValues as $br) {
$val = $resultsMatrix[$pp][$br] ?? 0;
$rowOutput[] = $val;
}
$this->line(implode("\t", $rowOutput));
}
}
}