partridgerocks/gmail-client 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

partridgerocks/gmail-client

最新稳定版本:v2.0.0

Composer 安装命令:

composer require partridgerocks/gmail-client

包简介

A gmail api connector

README 文档

README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads PHP Version Support Laravel Version Support

A Laravel package that integrates with the Gmail API to seamlessly manage emails within your application. Built with Saloon PHP for API interactions and Laravel Data for structured data handling.

📋 Table of Contents

🚀 Features

  • OAuth Authentication: Seamless integration with Gmail's OAuth 2.0 flow
  • Email Operations:
    • Read emails and threads with full content and attachments
    • Send emails with HTML content
    • Support for CC, BCC, and custom sender addresses
    • Automatic contact parsing for CRM integration
  • Label Management:
    • List, create, update, and delete email labels
    • Organize emails with custom label hierarchies
  • Performance Optimizations:
    • Batch statistics retrieval for multi-account dashboards
    • Smart count estimation to prevent timeouts on large mailboxes
    • Connection health monitoring with quota tracking
    • Lazy loading collections for memory-efficient processing
    • Pagination support for large datasets
    • Customizable batch sizes for API requests
  • Developer Experience:
    • Laravel facade for convenient access
    • Strongly-typed data objects with Laravel Data
    • Full Laravel service container integration
    • Comprehensive exception handling
    • Null-safe methods for robust applications (dashboards, background processing)
    • Command-line testing utilities
  • Enterprise Architecture:
    • Service layer with interface contracts for enhanced testability
    • Builder pattern for fluent client construction
    • Repository pattern for data access abstraction
    • Factory pattern for centralized service management
    • Type-safe configuration objects
    • Comprehensive test data builders and mock factories

📋 Requirements

  • PHP 8.2 or higher
  • Laravel 10.x, 11.x, or 12.x
  • Google API credentials

📦 Installation

You can install the package via Composer:

composer require partridgerocks/gmail-client

After installation, publish the configuration file:

php artisan vendor:publish --tag="gmail-client-config"

This will create a config/gmail-client.php configuration file in your project.

🔐 Google API Setup

Before you can use the Gmail Client, you need to set up a project in the Google Developer Console and obtain OAuth 2.0 credentials:

  1. Go to the Google Developer Console
  2. Create a new project (or select an existing one)
  3. Navigate to "APIs & Services" > "Library"
  4. Search for and enable the "Gmail API"
  5. Go to "APIs & Services" > "Credentials"
  6. Click "Configure Consent Screen" and set up your OAuth consent screen:
    • Select "External" or "Internal" user type (depending on your needs)
    • Fill in the required app information
    • Add the required scopes (see below)
    • Add test users if needed (for external user type)
  7. Create OAuth 2.0 credentials:
    • Go to "Credentials" and click "Create Credentials" > "OAuth client ID"
    • Select "Web application" as the application type
    • Add a name for your client
    • Add your authorized redirect URIs (this should match your GMAIL_REDIRECT_URI config value)
    • Click "Create"
  8. Copy the Client ID and Client Secret to your .env file:
GMAIL_CLIENT_ID=your-client-id
GMAIL_CLIENT_SECRET=your-client-secret
GMAIL_REDIRECT_URI=https://your-app.com/gmail/auth/callback
GMAIL_FROM_EMAIL=your-email@gmail.com

# Performance optimization settings
GMAIL_SMART_COUNTING=true
GMAIL_COUNT_THRESHOLD=50
GMAIL_CACHE_TTL=300
GMAIL_MAX_CONCURRENT=3
GMAIL_CIRCUIT_BREAKER=true
GMAIL_API_TIMEOUT=30

# Multi-account settings
GMAIL_MAX_ACCOUNTS=5
GMAIL_HEALTH_CHECK_INTERVAL=3600
GMAIL_BULK_OPERATIONS=true

Note: The Gmail API requires specific scopes to access different features. The default configuration includes commonly used scopes, but you can customize them in the config file.

🔍 Usage

The package automatically registers a Laravel Facade that provides a convenient way to interact with the Gmail API:

use PartridgeRocks\GmailClient\Facades\GmailClient;

// Get recent messages
$messages = GmailClient::listMessages();

// Get a specific message
$message = GmailClient::getMessage('message-id');

// Send an email
$email = GmailClient::sendEmail(
    'recipient@example.com',
    'Subject line',
    '<p>Email body in HTML format</p>'
);

Authentication

The Gmail Client provides two ways to authenticate with the Gmail API:

1. Manual Authentication Flow

If you want full control over the authentication process:

use PartridgeRocks\GmailClient\Facades\GmailClient;

// 1. Get the authorization URL
$authUrl = GmailClient::getAuthorizationUrl(
    config('gmail-client.redirect_uri'),
    config('gmail-client.scopes'),
    [
        'access_type' => 'offline',
        'prompt' => 'consent'
    ]
);

// 2. Redirect the user to the authorization URL
return redirect($authUrl);

// 3. In your callback route, exchange the code for tokens
public function handleCallback(Request $request)
{
    $code = $request->get('code');

    // Exchange code for tokens
    $tokens = GmailClient::exchangeCode(
        $code,
        config('gmail-client.redirect_uri')
    );

    // Store tokens securely for the authenticated user
    auth()->user()->update([
        'gmail_access_token' => $tokens['access_token'],
        'gmail_refresh_token' => $tokens['refresh_token'] ?? null,
        'gmail_token_expires_at' => now()->addSeconds($tokens['expires_in']),
    ]);

    return redirect()->route('dashboard');
}

2. Using Built-in Routes

For a simpler setup, you can enable the built-in routes:

  1. Enable route registration in the config file:
// config/gmail-client.php
'register_routes' => true,
  1. Use the provided routes in your app:
// Generate a link to the Gmail authentication page
<a href="{{ route('gmail.auth.redirect') }}">Connect Gmail</a>

The package will handle the OAuth flow and store the tokens in the session by default.

Working with Emails

Once authenticated, you can use the client to interact with Gmail:

List Messages

use PartridgeRocks\GmailClient\Facades\GmailClient;

// Authenticate with a stored token
GmailClient::authenticate($accessToken);

// List recent messages (returns a collection of Email data objects)
$messages = GmailClient::listMessages(['maxResults' => 10]);

foreach ($messages as $message) {
    echo "From: {$message->from}\n";
    echo "Subject: {$message->subject}\n";
    echo "Date: {$message->internalDate->format('Y-m-d H:i:s')}\n";
    echo "Body: {$message->body}\n";
}

// With query parameters (using Gmail search syntax)
$messages = GmailClient::listMessages([
    'q' => 'from:example@gmail.com after:2023/01/01 has:attachment',
    'maxResults' => 20
]);

Get a Specific Message

// Get a specific message by ID
$email = GmailClient::getMessage('message-id');

echo "Subject: {$email->subject}\n";
echo "From: {$email->from}\n";
echo "Snippet: {$email->snippet}\n";
echo "Body: {$email->body}\n";

// Access message headers
foreach ($email->headers as $name => $value) {
    echo "{$name}: {$value}\n";
}

// Check for specific labels
if (in_array('INBOX', $email->labelIds)) {
    echo "This message is in the inbox\n";
}

Send an Email

// Send a simple email
$email = GmailClient::sendEmail(
    'recipient@example.com',
    'Email subject',
    '<p>This is the email body in HTML format.</p>'
);

// Send with additional options
$email = GmailClient::sendEmail(
    'recipient@example.com',
    'Email with options',
    '<p>This email includes CC and BCC recipients.</p>',
    [
        'from' => 'your-email@gmail.com',
        'cc' => 'cc@example.com',
        'bcc' => 'bcc@example.com',
    ]
);

// The sent email object is returned
echo "Email sent with ID: {$email->id}";

Performance Features

Account Statistics (Batch Retrieval)

For multi-account dashboards, use the batch statistics method to minimize API calls:

// Get comprehensive account metrics in 1-2 API calls
$stats = GmailClient::getAccountStatistics([
    'unread_limit' => 25,        // Show exact count up to 25, then "25+"
    'today_limit' => 15,         // Today's messages limit  
    'include_labels' => true,    // Include label count
    'estimate_large_counts' => true,  // Use smart estimation
    'background_mode' => false,  // Throw exceptions or return partial data
]);

// Returns:
// [
//     'unread_count' => 23,           // or "25+" for large counts
//     'today_count' => 8,             // Today's messages
//     'labels_count' => 42,           // Total labels
//     'estimated_total' => 15000,     // Total mailbox size estimate
//     'api_calls_made' => 2,          // Actual API calls used
//     'last_updated' => '2024-01-01T12:00:00Z',
//     'partial_failure' => false,     // True if some metrics failed
// ]

// Performance comparison:
// Before: 3-5 API calls per account, 2-5s load time
// After:  1-2 API calls per account, <1s load time

Connection Health Monitoring

Monitor connection status and API quota:

$health = GmailClient::getAccountHealth();

// Returns:
// [
//     'connected' => true,
//     'status' => 'healthy',           // healthy, unhealthy, rate_limited, etc.
//     'api_quota_remaining' => 250,    // Remaining API calls (if available)
//     'last_successful_call' => '2024-01-01T12:00:00Z',
//     'errors' => [],                  // Array of error messages
// ]

// Use for dashboard health indicators
if ($health['status'] === 'rate_limited') {
    // Handle rate limiting gracefully
    $retryAfter = $health['retry_after'] ?? 60;
}

Contact Parsing & CRM Integration

The package automatically parses email addresses and names from Gmail messages, making it easy to integrate with CRM systems:

// Get an email message
$email = GmailClient::getMessage('message-id');

// Access parsed contact information
$sender = $email->fromContact;
echo "Sender: {$sender->name} <{$sender->email}>\n";
echo "Domain: {$sender->domain}\n";

// Access recipient contacts
foreach ($email->toContacts as $contact) {
    echo "To: {$contact->getDisplayName()} - {$contact->email}\n";
}

// Get all contacts involved in the email
$allContacts = $email->getAllContacts();

// Find contacts from specific domain (useful for CRM)
$externalContacts = array_filter(
    $allContacts,
    fn($contact) => !$contact->isFromDomain('mycompany.com')
);

// Get unique domains for company identification
$domains = $email->getContactDomains();
// Returns: ['example.com', 'client.com', 'mycompany.com']

// Check if email involves specific company
if ($email->hasContactFromDomain('important-client.com')) {
    // Handle VIP client email
}

// Get all contacts from a domain
$clientContacts = $email->getContactsFromDomain('acme-corp.com');

Manual Contact Parsing

You can also parse email strings manually:

use PartridgeRocks\GmailClient\Data\Contact;

// Parse single email address
$contact = Contact::parse('"John Doe" <john@example.com>');
echo $contact->name;    // "John Doe"  
echo $contact->email;   // "john@example.com"
echo $contact->domain;  // "example.com"

// Parse multiple addresses
$contacts = Contact::parseMultiple('john@example.com, "Jane Doe" <jane@example.com>');

// Access contact properties
foreach ($contacts as $contact) {
    echo "Name: " . ($contact->name ?? 'No name') . "\n";
    echo "Email: {$contact->email}\n";
    echo "Domain: {$contact->domain}\n";
    echo "Local part: {$contact->getLocalPart()}\n";
    echo "Display name: {$contact->getDisplayName()}\n";
    echo "Formatted: {$contact->format()}\n";
}

CRM Integration Examples

// Find all emails from a specific company
$companyEmails = collect($emails)->filter(function ($email) {
    return $email->hasContactFromDomain('target-company.com');
});

// Extract contact data for CRM import
$crmData = [];
foreach ($emails as $email) {
    foreach ($email->getAllContacts() as $contact) {
        if (!$contact->isFromDomain('mycompany.com')) {
            $crmData[] = [
                'name' => $contact->name,
                'email' => $contact->email,
                'company_domain' => $contact->domain,
                'first_contact' => $email->internalDate,
            ];
        }
    }
}

// Group contacts by company domain
$contactsByCompany = collect($emails)
    ->flatMap(fn($email) => $email->getAllContacts())
    ->groupBy('domain')
    ->map(fn($contacts) => $contacts->unique('email'));

Working with Labels

Gmail uses labels to organize emails. You can create, retrieve, and manage these labels:

// List all labels
$labels = GmailClient::listLabels();

foreach ($labels as $label) {
    echo "Label: {$label->name} (ID: {$label->id})\n";
    echo "Type: {$label->type}\n";
    
    if ($label->messagesTotal !== null) {
        echo "Messages: {$label->messagesTotal} ({$label->messagesUnread} unread)\n";
    }
}

// Get a specific label
$label = GmailClient::getLabel('label-id');

// Create a new label
$newLabel = GmailClient::createLabel('Important Clients', [
    'labelListVisibility' => 'labelShow',
    'messageListVisibility' => 'show',
    'color' => [
        'backgroundColor' => '#16a765',
        'textColor' => '#ffffff'
    ]
]);

// Update a label (using the LabelResource directly)
$updatedLabel = GmailClient::labels()->update($label->id, [
    'name' => 'VIP Clients',
    'color' => [
        'backgroundColor' => '#4986e7',
        'textColor' => '#ffffff'
    ]
]);

// Delete a label
GmailClient::labels()->delete($label->id);

Using Without Facade

If you prefer not to use the facade, you can resolve the client from the container:

use PartridgeRocks\GmailClient\GmailClient;

public function index(GmailClient $gmailClient)
{
    $gmailClient->authenticate($accessToken);
    $messages = $gmailClient->listMessages();
    
    // ...
}

Builder Pattern & Service Integration

For advanced usage with dependency injection and configuration:

use PartridgeRocks\GmailClient\Builders\GmailClientBuilder;
use PartridgeRocks\GmailClient\Contracts\MessageServiceInterface;

// Using builder pattern
$client = GmailClientBuilder::create()
    ->withToken($accessToken)
    ->withConfig($customConfig)
    ->build();

// Using service injection
class EmailController extends Controller
{
    public function __construct(private MessageServiceInterface $messageService) {}
    
    public function dashboard()
    {
        $recentMessages = $this->messageService->findRecent(10);
        return view('dashboard', compact('recentMessages'));
    }
}

Integration with Your User Model

Here's an example of how you might integrate the Gmail client with your User model:

// In your User model
public function connectGmail($accessToken, $refreshToken = null, $expiresAt = null)
{
    $this->gmail_access_token = $accessToken;
    $this->gmail_refresh_token = $refreshToken;
    $this->gmail_token_expires_at = $expiresAt;
    $this->save();
}

public function getGmailClient()
{
    if (empty($this->gmail_access_token)) {
        throw new \Exception('Gmail not connected');
    }
    
    return app(GmailClient::class)->authenticate(
        $this->gmail_access_token,
        $this->gmail_refresh_token,
        $this->gmail_token_expires_at ? new \DateTime($this->gmail_token_expires_at) : null
    );
}

// In your controller
public function listEmails()
{
    $gmailClient = auth()->user()->getGmailClient();
    return $gmailClient->listMessages(['maxResults' => 20]);
}

🔧 Advanced Usage

Pagination Support

The package supports pagination for listing messages and labels:

// Get a paginator for messages
$paginator = GmailClient::listMessages(['maxResults' => 25], true);

// Get the first page
$firstPage = $paginator->getNextPage();

// Check if there are more pages
if ($paginator->hasMorePages()) {
    // Get the next page
    $secondPage = $paginator->getNextPage();
}

// Or get all pages at once (use cautiously with large datasets)
$allMessages = $paginator->getAllPages();

// You can also transform the results using the DTO
use PartridgeRocks\GmailClient\Data\Responses\EmailDTO;
$emails = $paginator->transformUsingDTO(EmailDTO::class);

Memory Efficiency

When working with large Gmail accounts, it's important to avoid loading all messages into memory at once:

// Lazy loading is the most memory-efficient approach for large datasets
$messages = GmailClient::listMessages(lazy: true);

// Process messages one by one without loading everything into memory
foreach ($messages as $message) {
    processMessage($message);
    
    // You can stop iteration at any point
    if ($someCondition) {
        break;
    }
}

// For even more efficiency, you can get only message IDs without full details
$messageIds = GmailClient::listMessages(lazy: true, fullDetails: false);

foreach ($messageIds as $messageData) {
    echo "Message ID: {$messageData['id']}\n";
    
    // Load full details only for specific messages if needed
    if (needsFullDetails($messageData)) {
        $fullMessage = GmailClient::getMessage($messageData['id']);
    }
}

Null-Safe Methods for Robust Applications

For applications that need graceful degradation (dashboards, background processing), use the null-safe methods that never throw exceptions:

// ✅ Safe for dashboards - returns empty collection on any error
$labels = GmailClient::safeListLabels();
$messages = GmailClient::safeListMessages(['q' => 'is:unread']);

// ✅ Returns null instead of throwing NotFoundException
$message = GmailClient::safeGetMessage('message-id');

// ✅ Connection health check - never throws exceptions
if (GmailClient::isConnected()) {
    // Safe to proceed with Gmail operations
}

// ✅ Account overview with fallback data
$summary = GmailClient::getAccountSummary();
// Returns: ['connected' => true, 'labels_count' => 15, 'has_unread' => true, 'errors' => []]

// ✅ Statistics with graceful degradation
$stats = GmailClient::safeGetAccountStatistics();
// Returns fallback data if API fails: ['unread_count' => '?', 'partial_failure' => true]

Perfect for:

  • Dashboard widgets that should never crash
  • Background sync processes
  • Health monitoring systems
  • Any UI requiring robust error handling

Enhanced Error Handling

The package provides detailed error handling for common Gmail API errors:

use PartridgeRocks\GmailClient\Exceptions\AuthenticationException;
use PartridgeRocks\GmailClient\Exceptions\NotFoundException;
use PartridgeRocks\GmailClient\Exceptions\RateLimitException;
use PartridgeRocks\GmailClient\Exceptions\ValidationException;

try {
    $message = GmailClient::getMessage('non-existent-id');
} catch (NotFoundException $e) {
    // Handle not found error
    echo "Message not found: " . $e->getMessage();
} catch (AuthenticationException $e) {
    // Handle authentication errors
    echo "Authentication error: " . $e->getMessage();

    if ($e->getError()->code === 'token_expired') {
        // Refresh the token
        $tokens = GmailClient::refreshToken($refreshToken);
    }
} catch (RateLimitException $e) {
    // Handle rate limit errors
    $retryAfter = $e->getRetryAfter();
    echo "Rate limit exceeded. Retry after {$retryAfter} seconds.";
} catch (ValidationException $e) {
    // Handle validation errors
    echo "Validation error: " . $e->getMessage();
}

Refresh a Token

// Refresh an expired token
$tokens = GmailClient::refreshToken($refreshToken);

// The client is automatically authenticated with the new token
// Update the tokens in your storage
$user->update([
    'gmail_access_token' => $tokens['access_token'],
    'gmail_refresh_token' => $tokens['refresh_token'] ?? $user->gmail_refresh_token,
    'gmail_token_expires_at' => now()->addSeconds($tokens['expires_in']),
]);

Command Line Testing

The package includes a command to test your Gmail API connection:

# Get authentication URL
php artisan gmail-client:test --authenticate

# List recent messages
php artisan gmail-client:test --list-messages

# List labels
php artisan gmail-client:test --list-labels

Custom Email Templates

You can use your own branded email templates:

// config/gmail-client.php
'branded_template' => resource_path('views/emails/branded-template.blade.php'),

⚙️ Configuration

The package configuration file (config/gmail-client.php) provides extensive customization options:

Performance Settings

'performance' => [
    'enable_smart_counting' => true,           // Enable smart count estimation
    'count_estimation_threshold' => 50,        // Show exact count up to this limit
    'default_cache_ttl' => 300,               // Cache TTL in seconds (future use)
    'max_concurrent_requests' => 3,           // Max concurrent API requests (future use)
    'enable_circuit_breaker' => true,         // Enable circuit breaker pattern (future use)
    'api_timeout' => 30,                      // API request timeout in seconds
],

Multi-Account Settings

'multi_account' => [
    'max_accounts_per_user' => 5,             // Maximum Gmail accounts per user
    'health_check_interval' => 3600,          // Health check interval in seconds
    'enable_bulk_operations' => true,         // Enable bulk operations (future use)
],

Environment Variables

All configuration options can be overridden via environment variables:

Variable Default Description
GMAIL_SMART_COUNTING true Enable smart count estimation
GMAIL_COUNT_THRESHOLD 50 Threshold for showing exact vs estimated counts
GMAIL_CACHE_TTL 300 Cache time-to-live in seconds
GMAIL_MAX_CONCURRENT 3 Maximum concurrent requests
GMAIL_CIRCUIT_BREAKER true Enable circuit breaker pattern
GMAIL_API_TIMEOUT 30 API request timeout in seconds
GMAIL_MAX_ACCOUNTS 5 Maximum accounts per user
GMAIL_HEALTH_CHECK_INTERVAL 3600 Health check interval in seconds
GMAIL_BULK_OPERATIONS true Enable bulk operations

📡 Events

The package dispatches events that you can listen for:

  • GmailAccessTokenRefreshed: When a token is refreshed
  • GmailMessageSent: When an email is sent

🧪 Testing

The package includes tests that you can run with PHPUnit:

composer test

For testing in your own application, you can use Saloon's built-in mocking capabilities:

use Saloon\Laravel\Facades\Saloon;
use Saloon\Http\Faking\MockResponse;

// In your test setup
public function setUp(): void
{
    parent::setUp();
    
    // Mock all Gmail API responses
    Saloon::fake([
        '*gmail.googleapis.com*' => MockResponse::make([
            'messages' => [
                [
                    'id' => 'test-id-123',
                    'threadId' => 'thread-123',
                    'snippet' => 'This is a test email',
                ]
            ]
        ], 200),
    ]);
}

📝 Changelog

Please see CHANGELOG for more information on what has changed recently.

🤝 Contributing

Please see CONTRIBUTING for details.

🔒 Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

👨‍💻 Credits

📄 License

The MIT License (MIT). Please see License File for more information.

统计信息

  • 总下载量: 171
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 0
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 1
  • Forks: 1
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-05-13