Install the SDK:
composer require microsoft/microsoft-graph
Ensure PHP 8.2+ is used.
Register an Azure AD App:
User.Read, Mail.ReadWrite).tenantId, clientId, and clientSecret (or certificate).First Use Case: Fetch a User
use Microsoft\Graph\GraphServiceClient;
use Microsoft\Kiota\Authentication\Oauth\ClientCredentialContext;
$tokenRequestContext = new ClientCredentialContext(
'your-tenant-id',
'your-client-id',
'your-client-secret'
);
$client = new GraphServiceClient($tokenRequestContext);
$user = $client->users()->byUserId('user@domain.com')->get()->wait();
echo $user->getDisplayName();
Client Credentials (App-Only Auth):
ClientCredentialContext for background services (e.g., cron jobs, APIs).$client = new GraphServiceClient(
new ClientCredentialContext('tenantId', 'clientId', 'clientSecret')
);
Authorization Code (User Delegation):
AuthorizationCodeContext for web apps requiring user consent.authCode for a token.$tokenRequestContext = new AuthorizationCodeContext(
'tenantId', 'clientId', 'clientSecret', $authCode, 'https://your-app.com/callback'
);
On-Behalf-Of (Backend API Calls):
OnBehalfOfContext when a frontend app passes a user token to a backend.$tokenRequestContext = new OnBehalfOfContext(
'tenantId', 'clientId', 'clientSecret', $frontendAccessToken
);
Fetch Data (Async/Promise-Based):
$user = $client->me()->get()->wait(); // User.Read scope required
$emails = $client->users('user@domain.com')->messages()->get()->wait();
Create/Update Data:
$newUser = new \Microsoft\Graph\Model\User();
$newUser->setDisplayName('John Doe');
$createdUser = $client->users()->post($newUser)->wait();
Batch Requests:
$batch = $client->createBatch();
$batch->users('user@domain.com')->messages()->get();
$batch->users('user@domain.com')->calendar()->events()->get();
$results = $batch->execute()->wait();
Service Provider Setup:
Bind the client to Laravel’s container in AppServiceProvider:
public function register()
{
$this->app->singleton(GraphServiceClient::class, function ($app) {
$tokenRequestContext = new ClientCredentialContext(
config('services.graph.tenant_id'),
config('services.graph.client_id'),
config('services.graph.client_secret')
);
return new GraphServiceClient($tokenRequestContext);
});
}
Middleware for Token Refresh: Use Laravel middleware to handle token refreshes transparently:
public function handle($request, Closure $next)
{
$client = app(GraphServiceClient::class);
// Check token expiry and refresh if needed
return $next($request);
}
Jobs for Async Operations: Offload long-running Graph operations to Laravel queues:
use Microsoft\Graph\GraphServiceClient;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class SyncGraphData implements ShouldQueue
{
use Queueable;
public function handle(GraphServiceClient $client)
{
$client->users()->get()->wait();
}
}
Token Expiry Handling:
use Microsoft\Kiota\Authentication\Cache\RedisAccessTokenCache;
use Predis\Client as RedisClient;
$redis = new RedisClient(['scheme' => 'tcp', 'host' => 'localhost']);
$cache = new RedisAccessTokenCache($redis);
$client = GraphServiceClient::createWithAuthenticationProvider(
GraphPhpLeagueAuthenticationProvider::createWithAccessTokenProvider(
GraphPhpLeagueAccessTokenProvider::createWithCache($cache)
)
);
Async Operations:
.wait() on promises will leave requests unresolved. Always chain .wait() for synchronous behavior.// ❌ Missing .wait() – request fires but result is ignored
$client->me()->get();
Scope Mismatches:
403 Forbidden errors.User.Read is required for $client->me()->get().National Clouds:
graph.microsoft.com. For China/Germany clouds, specify:
$client = new GraphServiceClient($tokenRequestContext, [], NationalCloud::CHINA);
Enable SDK Logging: Configure Guzzle to log requests/responses:
$guzzleConfig = [
'debug' => fopen('graph_debug.log', 'w'),
'curl' => function ($curl) {
$curl->setopt(CURLOPT_VERBOSE, true);
}
];
$httpClient = GraphClientFactory::createWithConfig($guzzleConfig);
Handle API Exceptions:
Catch Microsoft\Kiota\Abstractions\ApiException for Graph-specific errors:
try {
$user = $client->users()->byUserId('user@domain.com')->get()->wait();
} catch (ApiException $e) {
if ($e->getStatusCode() === 401) {
// Token expired or invalid
}
throw $e;
}
Use Strong Typing: Leverage PHP’s return types for Graph models:
/** @var \Microsoft\Graph\Model\User $user */
$user = $client->me()->get()->wait();
Pagination:
Handle paginated responses with $nextLink:
$users = $client->users()->get()->wait();
while ($users->getODataNextLink()) {
$users = $client->getRequestAdapter()
->getRequestInformation($users->getODataNextLink())
->wait();
}
Rate Limiting: Microsoft Graph enforces throttling policies. Implement exponential backoff for retries:
use Symfony\Component\HttpClient\RetryableHttpClient;
use Symfony\Component\HttpClient\Retry\RetryStrategy;
$retryStrategy = new RetryStrategy(3, 1000); // 3 retries, 1s delay
$httpClient = new RetryableHttpClient($guzzleClient, $retryStrategy);
Environment-Specific Config:
Store credentials in Laravel’s .env:
GRAPH_TENANT_ID=your-tenant-id
GRAPH_CLIENT_ID=your-client-id
GRAPH_CLIENT_SECRET=your-client-secret
Then inject into ClientCredentialContext:
$tokenRequestContext = new ClientCredentialContext(
config('services.graph.tenant_id'),
config('services.graph.client_id'),
config('services.graph.client_secret')
);
Testing: Use the Graph Explorer to test endpoints before implementing. Mock the SDK in PHPUnit:
$mockClient = $this->createMock(GraphServiceClient::class);
$mockClient->method('users')->willReturnSelf();
$mockClient->method('byUserId')->willReturnSelf();
$mockClient->method('get')->willReturn(new Promise(function ($resolve) {
$resolve(new \Microsoft\Graph\Model\User());
}));
How can I help you explore Laravel packages today?