0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LaravelとGA4 連携

Last updated at Posted at 2025-01-16

すでに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));
        }
    }
}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?