kreait / laravel-firebase

A Laravel package for the Firebase PHP Admin SDK
https://github.com/kreait/firebase-php
MIT License
992 stars 163 forks source link

Multiple projects on latest release #148

Closed nicolasvahidzein closed 1 year ago

nicolasvahidzein commented 1 year ago

Hello @jeromegamez! How are you?

I had to bite the bullet and upgrade my laravel installation to the latest one. I am on v 9.19 now and all should be better now.

I just wanted to clarify the following:

in the config file for laravel i added to entries under project like so:

'projects' => [
        'client_mobile_app' => [
            'credentials' => [
                'file' => env('FIREBASE_CREDENTIALS', env('GOOGLE_APPLICATION_CREDENTIALS')),

                /*
                 * If you want to prevent the auto discovery of credentials, set the
                 * following parameter to false. If you disable it, you must
                 * provide a credentials file.
                 */
                'auto_discovery' => true,
            ],

        ],
        'admin_mobile_app' => [
            'credentials' => [
                'file' => env('FIREBASE_CREDENTIALS_ADMIN_APP', env('GOOGLE_APPLICATION_CREDENTIALS')),

                /*
                 * If you want to prevent the auto discovery of credentials, set the
                 * following parameter to false. If you disable it, you must
                 * provide a credentials file.
                 */
                'auto_discovery' => true,
            ],

        ],
    ],
];

So now according to the instructions i should use these two variables to call each projecet: admin_mobile_app/client_mobile_app

except i'm confused on how to do it exactly, the readme stops at at how to use it for auth.

i used to have this with the provider you built for me (thanks a million btw):

//for one project
$messaging = app('firebase.admin_mobile_app.messaging');
//or for the other project
$messaging = app('firebase.client_mobile_app.messaging');

$sendingInfo = $messaging->send($message);

but now, how do I do it? Is this correct?

$messaging = admin_mobile_app();
$messaging = client_mobile_app();

$sendingInfo = $messaging->send($message);

or this route? (i must remove auth i am guessing):

$messaging = Firebase::project('client_mobile_app')->auth();
$messaging = Firebase::project('admin_mobile_app')->auth();

$sendingInfo = $messaging->send($message);
jeromegamez commented 1 year ago

Hey @nicolasvahidzein! 👋

The Firebase::project() route is the one I would use to access multiple projects, so you're almost there!

// Will return the Auth Component of the client mobile app
// and is an instance of Kreait\Firebase\Contract\Auth
$auth = Firebase::project('client_mobile_app')->auth();

// Will return the Messaging Component of the admin mobile app
// and is an instance of Kreait\Firebase\Contract\Messaging
$messaging = Firebase::project('admin_mobile_app')->messaging();

The app(...) notation will only work in applications that use one single project.

You can find available methods to get the components you might need in https://github.com/kreait/laravel-firebase/blob/main/src/FirebaseProject.php.

If you want to use multiple projects inside of one Application Service in your project, you could do something like this:

use Kreait\Laravel\Firebase\FirebaseProject;
use Kreait\Laravel\Firebase\FirebaseProjectManager;

class MyService
{
    private FirebaseProject $clientMobileApp;
    private FirebaseProject $adminMobileApp;

    public function __construct(FirebaseProjectManager $manager)
    {
        $this->clientMobileApp = $manager->project('client_mobile_app');
        $this->adminMobileApp = $manager->project('admin_mobile_app');
    }

    public function doSomething()
    {
        // ...
    }
}

I hope this helps!

nicolasvahidzein commented 1 year ago

You are the best as usual. Thanks a million. I'll revert back as soon as i have the new project up and running.

nicolasvahidzein commented 1 year ago

Are these still valid @jeromegamez?

I noticed the namespace changed on the main functions.

use Kreait\Firebase\Messaging\RawMessageFromArray; use Kreait\Firebase\Exception\Messaging\InvalidArgument; use Kreait\Firebase\Exception\Messaging\NotFound; use Kreait\Firebase\Exception\Messaging\InvalidMessage;

jeromegamez commented 1 year ago

Yes, they should still be valid, but you can always check on the classes/exceptions themselves, either in the vendor/kreait/firebase-php directory, or in the web UI at https://github.com/kreait/firebase-php 😅

If you want to see what changed between the 5.x and 6.x releases, the Changelog can help: https://github.com/kreait/firebase-php/blob/6.x/CHANGELOG.md#600---2022-01-07

nicolasvahidzein commented 1 year ago

Wonderful. Thanks Jerome!

nicolasvahidzein commented 1 year ago

Quick question Jerome, i am getting this error in VSCODE:

syntax error, unexpected 'FirebaseProject' (T_STRING), expecting function (T_FUNCTION) or const (T_CONST)

001

Is it just my IDE or did I forget something?

jeromegamez commented 1 year ago

It's either your IDE or the underlying PHP version is too old. The package and the SDK need at least PHP 7.4

jeromegamez commented 1 year ago

I'm currently on the phone so I can't be sure that I'm 100% accurate, but you can configure the used PHP version in PHPStorm in the Preferences. It should be something like "Local interpreters" or "Remote interpreters" or "Language Level". There you should be able to change the PHP version for the current project 🤞🏻

nicolasvahidzein commented 1 year ago

Wonderful, thank you for your guidance Jerome. I will look into it and revert back. Good day to you.

nicolasvahidzein commented 1 year ago

sorry again for the bother Jerome, I don't get where i should be passing the manager from nor where to get it:

FirebaseProjectManager $manager

from here:

public function __construct(FirebaseProjectManager $manager)

I have been looking inside the package for mentions of manager and i found this:

$manager = $this->app->make(FirebaseProjectManager::class);

BUT i do not know where i place this nor where i call it from. I get the following error when i try to pass that line of code from the parent function to pass this argument:

local.ERROR: Undefined property: App\Methods\Notifications\SendNotification::$app {"userId":17,"exception":"[object] (ErrorException(code: 0): Undefined property: App\\Methods\\Notifications\\SendNotification::$app at /var/www/html/zeintek/merlin/back_end2/app/Methods/Notifications/SendNotification.php:368)
jeromegamez commented 1 year ago

Nicolas, I hope you know that what we're doing here is not "I found a bug" but "individual support for GitHub Sponsors in Slack" stuff 😅, (please read this in a friendly tone).

I'll update the Readme with a usage example once I can reach my computer so that others can benefit from it in a more accessible place than here in this thread 🤞🏻

jeromegamez commented 1 year ago

I just got the notification about your donation, thank you very much! 🥰 Then let's continue 🫣😂

nicolasvahidzein commented 1 year ago

I know I am a pain right now, my apologies Jerome. I know you mean it nicely. It was actually a good reminder. Check your sponsor page!!!

PS: I THINK I got it to work but the way i did it is not as elegant as yours:

        $manager = $this->app->make(FirebaseProjectManager::class);

        $clientMobileApp = $manager->project('client_mobile_app');
        $adminMobileApp = $manager->project('admin_mobile_app');
jeromegamez commented 1 year ago

That's exactly the way to go and will work! But there's an even more elegant way to do it, I'm sure you'll like it! Give me an hour or two, I need my computer to write it up 🙏🏻

nicolasvahidzein commented 1 year ago

ok perfect. Thank you Jerome.

nicolasvahidzein commented 1 year ago

BTW: same error:

$app {"exception":"[object] (ErrorException(code: 0): Undefined property: App\Jobs\PushMessaging\SendPushNotification::$app at /var/www/html/zeintek/merlin/back_end2/app/Jobs/PushMessaging/

jeromegamez commented 1 year ago

As far as I know, and if I guess this right from the error, you don't have the app available in a job. You need to inject the FirebaseProjectManager into the job by adding it as a parameter to the handle() method.

nicolasvahidzein commented 1 year ago

Ok let me try it

nicolasvahidzein commented 1 year ago

In your example a while back you were injecting it in the construct method i think. I will see if it works in the handle method.

jeromegamez commented 1 year ago

For services, yes, but jobs work differently

https://laravel.com/docs/9.x/queues#handle-method-dependency-injection

nicolasvahidzein commented 1 year ago

I'm lost with this. I don't mind passing it in the construct method i just do not know how to pass it, or I am doing something wrong.

I'll wait until you are in front of a computer for a complete example since i'm not an expert and this dependency injection is throwing me for a loop.

nicolasvahidzein commented 1 year ago

BTW, i'm already passing 7 arguments and so when i had the dependency injection as the 8th parameter i get an error. Just wanted to clarify.

jeromegamez commented 1 year ago

Alright, here's how I set up an example project

$ composer create-project laravel/laravel laravel9-with-firebase
$ cd laravel9-with-firebase
$ php artisan vendor:publish --provider="Kreait\Laravel\Firebase\ServiceProvider" --tag=config
$ php artisan make:controller FirebaseController
$ php artisan make:job SendFirebasePushNotification

Config

# .env

APP_NAME=Laravel
APP_ENV=local

# all other variables unchanged and added at the bottom:
FIREBASE_PROJECT_A_CREDENTIALS=/path/to/project_a_credentials.json
FIREBASE_PROJECT_A_DB_URL=https://project-a.firebaseio.com

FIREBASE_PROJECT_B_CREDENTIALS=/path/to/project_b_credentials.json
FIREBASE_PROJECT_B_DB_URL=https://project-b.firebaseio.com
<?php

# config/firebase.php

return [
    /*
     * ------------------------------------------------------------------------
     * Default Firebase project
     * ------------------------------------------------------------------------
     */
    'default' => env('FIREBASE_PROJECT', 'project_a'),

    /*
     * ------------------------------------------------------------------------
     * Firebase project configurations
     * ------------------------------------------------------------------------
     */
    'projects' => [
        'project_a' => [
            'credentials' => [
                'file' => env('FIREBASE_PROJECT_A_CREDENTIALS'),
            ],
            'database' => [
                'url' => env('FIREBASE_PROJECT_A_DB_URL'),
            ],
        ],
        'project_b' => [
            'credentials' => [
                'file' => env('FIREBASE_PROJECT_B_CREDENTIALS'),
            ],
            'database' => [
                'url' => env('FIREBASE_PROJECT_B_DB_URL'),
            ],
        ],
    ],
];
<?php

# routes/web.php

use App\Http\Controllers\FirebaseUserController;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Route::get('firebase/users/{projectId}', [FirebaseUserController::class, 'index']);

Using the project manager in a controller

<?php
# app/Http/Controllers/FirebaseUserController

namespace App\Http\Controllers;

use Kreait\Laravel\Firebase\FirebaseProjectManager;

class FirebaseUserController extends Controller
{
    public function __construct(private FirebaseProjectManager $firebase)
    {
    }

    public function index(string $projectId)
    {
        return $this->firebase
            ->project($projectId)
            ->auth()
            ->queryUsers([
                'limit' => 2
            ]);
    }
}

Using the project manager in a job

<?php
# app/Jobs/SendFirebasePushNotification

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Kreait\Firebase\Messaging\CloudMessage;
use Kreait\Firebase\Messaging\Notification;
use Kreait\Laravel\Firebase\FirebaseProjectManager;

class SendFirebasePushNotification implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        public string $projectId,
        public string $registrationToken
    ) {}

    public function handle(FirebaseProjectManager $projectManager): void
    {
        $message = [
            'token' => $this->registrationToken,
            'notification' => [
                'title' => 'Notification title',
                'body' => 'Notification body'
            ],
        ];

        $result = $projectManager
            ->project($this->projectId)
            ->messaging()
            ->send($message);

        Log::info('Push notification sent', [
            'message' => $message,
            'result' => $result,
        ]);
    }
}

Testing

Controller

Assuming that the application is available at http://laravel9-with-firebase.test/, you can now call

and will get users from the respective project

Job

You can test the Job with laravel tinker (you need a real, working registration to replay this without errors):

$ php artisan tinker
Psy Shell v0.11.8 (PHP 8.1.9 — cli) by Justin Hileman
>>> App\Jobs\SendFirebasePushNotification::dispatchSync('project_a', 'registration_token');
=> 0

This will result in a new entry in storage/logs/laravel.log:

[2022-09-03 17:54:17] local.INFO: Push notification sent {"message":{"token":"registration_token","notification":{"title":"Notification title","body":"Notification body"}},"result":{"name":"projects/beste-firebase/messages/db11e06b-a585-4f3f-b702-b80044b1b185"}}

Depending on how you do things in your project, you could also create the Message beforehand and pass that to the job:

<?php

# app/Jobs/SendFirebaseMessage.php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Kreait\Firebase\Messaging\Message;
use Kreait\Laravel\Firebase\FirebaseProjectManager;
use Throwable;

class SendFirebaseMessage implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        public string $project,
        public Message $message
    )
    {
    }

    public function handle(FirebaseProjectManager $projectManager)
    {
        try {
            $projectManager->project($this->project)
                ->messaging()
                ->send($this->message);

            Log::info('Message sent');
        } catch (Throwable $e) {
            Log::warning('The message could not be sent', [
                'exception' => $e
            ]);
        }
    }
}

I hope this helps! I think I will use the opportunity to create an example application similar to https://github.com/jeromegamez/firebase-php-examples but for Laravel, now that I've set it up anyway :D

jeromegamez commented 1 year ago

I'm closing this because the OP was long done. You can continue to add comments, but don't need to reopen the issue, I get notified anyway 😊

nicolasvahidzein commented 1 year ago

Ok I thought i had to reopen it. Sorry. Thanks for the code. I'll take the time to read it in detail and revert back. Thanks so much.

nicolasvahidzein commented 1 year ago

All is well now thanks @jeromegamez

The issue was passing it in the handle and not the constructor. Thank you so much.

jeromegamez commented 1 year ago

Glad it worked out!