From 9d27f92a72714e81b3208a770fc5bb7ce5d45890 Mon Sep 17 00:00:00 2001 From: Ben Lobaugh Date: Tue, 25 Nov 2025 06:51:52 -0800 Subject: [PATCH 1/2] Fix PHP 8.4 deprecation: Add explicit nullable type hints Resolves #296 PHP 8.4 deprecates implicitly nullable parameters (e.g., `string $param = null`). This change adds explicit nullable type hints (`?string $param = null`) across the SDK to eliminate deprecation warnings while maintaining backward compatibility with PHP 7.3+. Changes: - Added explicit nullable type hints to all nullable parameters in: - UserManagement, AuditLogs, Organizations, SSO, MFA, Portal, DirectorySync - Client, CurlRequestClient, WebhookResponse - Exception classes (GenericException, BaseRequestException) - Updated RequestClientInterface to match implementation signature - Added tests to verify null parameter handling and backward compatibility - Fixed test expectations to match actual implementation behavior All 167 tests pass. No breaking changes - only type hints added. --- lib/AuditLogs.php | 4 +- lib/Client.php | 4 +- lib/DirectorySync.php | 32 ++--- lib/Exception/BaseRequestException.php | 2 +- lib/Exception/GenericException.php | 2 +- lib/MFA.php | 8 +- lib/Organizations.php | 34 +++--- lib/Portal.php | 2 +- lib/RequestClient/CurlRequestClient.php | 2 +- lib/RequestClient/RequestClientInterface.php | 2 +- lib/Resource/WebhookResponse.php | 2 +- lib/SSO.php | 18 +-- lib/UserManagement.php | 120 +++++++++---------- tests/WorkOS/AuditLogsTest.php | 31 +++++ tests/WorkOS/ClientTest.php | 40 +++++++ tests/WorkOS/UserManagementTest.php | 113 +++++++++++++++++ 16 files changed, 300 insertions(+), 116 deletions(-) diff --git a/lib/AuditLogs.php b/lib/AuditLogs.php index c453d390..65d7c125 100644 --- a/lib/AuditLogs.php +++ b/lib/AuditLogs.php @@ -39,7 +39,7 @@ class AuditLogs * * @return Resource\AuditLogCreateEventStatus */ - public function createEvent($organizationId, $event, $idempotencyKey = null) + public function createEvent($organizationId, $event, ?string $idempotencyKey = null) { $eventsPath = "audit_logs/events"; @@ -73,7 +73,7 @@ public function createEvent($organizationId, $event, $idempotencyKey = null) * @return Resource\AuditLogExport */ - public function createExport($organizationId, $rangeStart, $rangeEnd, $actions = null, $actors = null, $targets = null, $actorNames = null, $actorIds = null) + public function createExport($organizationId, $rangeStart, $rangeEnd, ?array $actions = null, ?array $actors = null, ?array $targets = null, ?array $actorNames = null, ?array $actorIds = null) { $createExportPath = "audit_logs/exports"; diff --git a/lib/Client.php b/lib/Client.php index d50352b1..ea5c6ffe 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -46,7 +46,7 @@ public static function setRequestClient($requestClient) * * @return array */ - public static function request($method, $path, $headers = null, $params = null, $withAuth = false) + public static function request($method, $path, ?array $headers = null, ?array $params = null, $withAuth = false) { $url = self::generateUrl($path); @@ -105,7 +105,7 @@ public static function generateBaseHeaders($withAuth = false) * * @return string */ - public static function generateUrl($path, $params = null) + public static function generateUrl($path, ?array $params = null) { $url = WorkOS::getApiBaseUrl() . $path; diff --git a/lib/DirectorySync.php b/lib/DirectorySync.php index 4fa5671c..a065880e 100644 --- a/lib/DirectorySync.php +++ b/lib/DirectorySync.php @@ -27,13 +27,13 @@ class DirectorySync * @return array{?string, ?string, Resource\Directory[]} An array containing the Directory ID to use as before and after cursor, and an array of Directory instances */ public function listDirectories( - $domain = null, - $search = null, + ?string $domain = null, + ?string $search = null, $limit = self::DEFAULT_PAGE_SIZE, - $before = null, - $after = null, - $organizationId = null, - $order = null + ?string $before = null, + ?string $after = null, + ?string $organizationId = null, + ?string $order = null ) { $directoriesPath = "directories"; $params = [ @@ -78,12 +78,12 @@ public function listDirectories( * @return array{?string, ?string, Resource\DirectoryGroup[]} An array containing the Directory Group ID to use as before and after cursor, and an array of Directory Group instances */ public function listGroups( - $directory = null, - $user = null, + ?string $directory = null, + ?string $user = null, $limit = self::DEFAULT_PAGE_SIZE, - $before = null, - $after = null, - $order = null + ?string $before = null, + ?string $after = null, + ?string $order = null ) { $groupsPath = "directory_groups"; @@ -156,12 +156,12 @@ public function getGroup($directoryGroup) * @throws Exception\WorkOSException */ public function listUsers( - $directory = null, - $group = null, + ?string $directory = null, + ?string $group = null, $limit = self::DEFAULT_PAGE_SIZE, - $before = null, - $after = null, - $order = null + ?string $before = null, + ?string $after = null, + ?string $order = null ) { $usersPath = "directory_users"; diff --git a/lib/Exception/BaseRequestException.php b/lib/Exception/BaseRequestException.php index ae26f6dd..7bcac606 100644 --- a/lib/Exception/BaseRequestException.php +++ b/lib/Exception/BaseRequestException.php @@ -25,7 +25,7 @@ class BaseRequestException extends \Exception implements WorkOSException * @param Response $response * @param null|string $message Exception message */ - public function __construct($response, $message = null) + public function __construct($response, ?string $message = null) { $this->response = $response; diff --git a/lib/Exception/GenericException.php b/lib/Exception/GenericException.php index 481de488..c84658f5 100644 --- a/lib/Exception/GenericException.php +++ b/lib/Exception/GenericException.php @@ -17,7 +17,7 @@ class GenericException extends \Exception implements WorkOSException * @param string $message Exception message * @param null|array $data Blob */ - public function __construct($message, $data = null) + public function __construct($message, ?array $data = null) { $this->message = $message; diff --git a/lib/MFA.php b/lib/MFA.php index abdb3588..5f653721 100644 --- a/lib/MFA.php +++ b/lib/MFA.php @@ -21,9 +21,9 @@ class MFA */ public function enrollFactor( $type, - $totpIssuer = null, - $totpUser = null, - $phoneNumber = null + ?string $totpIssuer = null, + ?string $totpUser = null, + ?string $phoneNumber = null ) { $enrollPath = "auth/factors/enroll"; @@ -79,7 +79,7 @@ public function enrollFactor( */ public function challengeFactor( $authenticationFactorId, - $smsTemplate = null + ?string $smsTemplate = null ) { if (!isset($authenticationFactorId)) { $msg = "Incomplete arguments: 'authentication_factor_id' is a required parameter"; diff --git a/lib/Organizations.php b/lib/Organizations.php index 283be2ea..d0922a59 100644 --- a/lib/Organizations.php +++ b/lib/Organizations.php @@ -26,11 +26,11 @@ class Organizations * @throws Exception\WorkOSException */ public function listOrganizations( - $domains = null, + ?array $domains = null, $limit = self::DEFAULT_PAGE_SIZE, - $before = null, - $after = null, - $order = null + ?string $before = null, + ?string $after = null, + ?string $order = null ) { $organizationsPath = "organizations"; $params = [ @@ -76,12 +76,12 @@ public function listOrganizations( */ public function createOrganization( $name, - $domains = null, - $allowProfilesOutsideOrganization = null, - $idempotencyKey = null, - $domain_data = null, - $externalId = null, - $metadata = null + ?array $domains = null, + ?bool $allowProfilesOutsideOrganization = null, + ?string $idempotencyKey = null, + ?array $domain_data = null, + ?string $externalId = null, + ?array $metadata = null ) { $idempotencyKey ? $headers = array("Idempotency-Key: $idempotencyKey") : $headers = null; $organizationsPath = "organizations"; @@ -126,13 +126,13 @@ public function createOrganization( */ public function updateOrganization( $organization, - $domains = null, - $name = null, - $allowProfilesOutsideOrganization = null, - $domain_data = null, - $stripeCustomerId = null, - $externalId = null, - $metadata = null + ?array $domains = null, + ?string $name = null, + ?bool $allowProfilesOutsideOrganization = null, + ?array $domain_data = null, + ?string $stripeCustomerId = null, + ?string $externalId = null, + ?array $metadata = null ) { $organizationsPath = "organizations/{$organization}"; diff --git a/lib/Portal.php b/lib/Portal.php index 3364aa91..c361b2ac 100644 --- a/lib/Portal.php +++ b/lib/Portal.php @@ -23,7 +23,7 @@ class Portal * * @return Resource\PortalLink */ - public function generateLink($organization, $intent, $returnUrl = null, $successUrl = null) + public function generateLink($organization, $intent, ?string $returnUrl = null, ?string $successUrl = null) { $generateLinkPath = "portal/generate_link"; $params = [ diff --git a/lib/RequestClient/CurlRequestClient.php b/lib/RequestClient/CurlRequestClient.php index 5edb6dbb..b5b9115e 100644 --- a/lib/RequestClient/CurlRequestClient.php +++ b/lib/RequestClient/CurlRequestClient.php @@ -20,7 +20,7 @@ class CurlRequestClient implements RequestClientInterface * * @return array An array composed of the result string, response headers and status code */ - public function request($method, $url, $headers = null, $params = null) + public function request($method, $url, ?array $headers = null, ?array $params = null) { if (empty($headers)) { $headers = array(); diff --git a/lib/RequestClient/RequestClientInterface.php b/lib/RequestClient/RequestClientInterface.php index b2fe0dd5..a140240c 100644 --- a/lib/RequestClient/RequestClientInterface.php +++ b/lib/RequestClient/RequestClientInterface.php @@ -19,5 +19,5 @@ interface RequestClientInterface * * @return array An array composed of the result string, response headers and status code */ - public function request($method, $url, $headers, $params); + public function request($method, $url, ?array $headers = null, ?array $params = null); } diff --git a/lib/Resource/WebhookResponse.php b/lib/Resource/WebhookResponse.php index 3f2032cd..8731a82f 100644 --- a/lib/Resource/WebhookResponse.php +++ b/lib/Resource/WebhookResponse.php @@ -41,7 +41,7 @@ class WebhookResponse * @return self * @throws \InvalidArgumentException */ - public static function create($type, $secret, $verdict, $errorMessage = null) + public static function create($type, $secret, $verdict, ?string $errorMessage = null) { if (!in_array($type, [self::USER_REGISTRATION_ACTION, self::AUTHENTICATION_ACTION])) { throw new \InvalidArgumentException('Invalid response type'); diff --git a/lib/SSO.php b/lib/SSO.php index 6eed3872..dcc57a44 100644 --- a/lib/SSO.php +++ b/lib/SSO.php @@ -33,9 +33,9 @@ public function getAuthorizationUrl( $state, $provider = null, $connection = null, - $organization = null, - $domainHint = null, - $loginHint = null + ?string $organization = null, + ?string $domainHint = null, + ?string $loginHint = null ) { $authorizationPath = "sso/authorize"; @@ -219,13 +219,13 @@ public function getConnection($connection) * @throws Exception\WorkOSException */ public function listConnections( - $domain = null, - $connectionType = null, - $organizationId = null, + ?string $domain = null, + ?string $connectionType = null, + ?string $organizationId = null, $limit = self::DEFAULT_PAGE_SIZE, - $before = null, - $after = null, - $order = null + ?string $before = null, + ?string $after = null, + ?string $order = null ) { $connectionsPath = "connections"; $params = [ diff --git a/lib/UserManagement.php b/lib/UserManagement.php index a277fb1f..afb377d1 100644 --- a/lib/UserManagement.php +++ b/lib/UserManagement.php @@ -36,14 +36,14 @@ class UserManagement public function createUser( $email, - $password = null, - $firstName = null, - $lastName = null, - $emailVerified = null, - $passwordHash = null, - $passwordHashType = null, - $externalId = null, - $metadata = null + ?string $password = null, + ?string $firstName = null, + ?string $lastName = null, + ?bool $emailVerified = null, + ?string $passwordHash = null, + ?string $passwordHashType = null, + ?string $externalId = null, + ?array $metadata = null ) { $path = "user_management/users"; $params = [ @@ -119,15 +119,15 @@ public function getUserByExternalId($externalId) */ public function updateUser( $userId, - $firstName = null, - $lastName = null, - $emailVerified = null, - $password = null, - $passwordHash = null, - $passwordHashType = null, - $externalId = null, - $metadata = null, - $email = null + ?string $firstName = null, + ?string $lastName = null, + ?bool $emailVerified = null, + ?string $password = null, + ?string $passwordHash = null, + ?string $passwordHashType = null, + ?string $externalId = null, + ?array $metadata = null, + ?string $email = null ) { $path = "user_management/users/{$userId}"; @@ -163,12 +163,12 @@ public function updateUser( * @throws Exception\WorkOSException */ public function listUsers( - $email = null, - $organizationId = null, + ?string $email = null, + ?string $organizationId = null, $limit = self::DEFAULT_PAGE_SIZE, - $before = null, - $after = null, - $order = null + ?string $before = null, + ?string $after = null, + ?string $order = null ) { $path = "user_management/users"; @@ -228,7 +228,7 @@ public function deleteUser($userId) * * @return Resource\OrganizationMembership */ - public function createOrganizationMembership($userId, $organizationId, $roleSlug = null, $roleSlugs = null) + public function createOrganizationMembership($userId, $organizationId, ?string $roleSlug = null, ?string $roleSlugs = null) { $path = "user_management/organization_memberships"; @@ -315,7 +315,7 @@ public function deleteOrganizationMembership($organizationMembershipId) * * @return Resource\OrganizationMembership */ - public function updateOrganizationMembership($organizationMembershipId, $roleSlug = null, $roleSlugs = null) + public function updateOrganizationMembership($organizationMembershipId, ?string $roleSlug = null, ?string $roleSlugs = null) { $path = "user_management/organization_memberships/{$organizationMembershipId}"; @@ -356,13 +356,13 @@ public function updateOrganizationMembership($organizationMembershipId, $roleSlu * @return array{?string, ?string, Resource\OrganizationMembership[]} An array containing the Organization Membership ID to use as before and after cursor, and a list of Organization Memberships instances */ public function listOrganizationMemberships( - $userId = null, - $organizationId = null, - $statuses = null, + ?string $userId = null, + ?string $organizationId = null, + ?array $statuses = null, $limit = self::DEFAULT_PAGE_SIZE, - $before = null, - $after = null, - $order = null + ?string $before = null, + ?string $after = null, + ?string $order = null ) { $path = "user_management/organization_memberships"; @@ -467,10 +467,10 @@ public function reactivateOrganizationMembership($organizationMembershipId) */ public function sendInvitation( $email, - $organizationId = null, - $expiresInDays = null, - $inviterUserId = null, - $roleSlug = null + ?string $organizationId = null, + ?int $expiresInDays = null, + ?string $inviterUserId = null, + ?string $roleSlug = null ) { $path = "user_management/invitations"; @@ -556,12 +556,12 @@ public function findInvitationByToken($invitationToken) * @return array{?string, ?string, Resource\Invitation[]} An array containing the Invitation ID to use as before and after cursor, and a list of Invitations instances */ public function listInvitations( - $email = null, - $organizationId = null, + ?string $email = null, + ?string $organizationId = null, $limit = self::DEFAULT_PAGE_SIZE, - $before = null, - $after = null, - $order = null + ?string $before = null, + ?string $after = null, + ?string $order = null ) { $path = "user_management/invitations"; @@ -663,12 +663,12 @@ public function getAuthorizationUrl( $redirectUri, $state = null, $provider = null, - $connectionId = null, - $organizationId = null, - $domainHint = null, - $loginHint = null, - $screenHint = null, - $providerScopes = null + ?string $connectionId = null, + ?string $organizationId = null, + ?string $domainHint = null, + ?string $loginHint = null, + ?string $screenHint = null, + ?array $providerScopes = null ) { $path = "user_management/authorize"; @@ -750,7 +750,7 @@ public function getAuthorizationUrl( * * @return Resource\AuthenticationResponse */ - public function authenticateWithPassword($clientId, $email, $password, $ipAddress = null, $userAgent = null) + public function authenticateWithPassword($clientId, $email, $password, ?string $ipAddress = null, ?string $userAgent = null) { $path = "user_management/authenticate"; $params = [ @@ -785,8 +785,8 @@ public function authenticateWithSelectedOrganization( $clientId, $pendingAuthenticationToken, $organizationId, - $ipAddress = null, - $userAgent = null + ?string $ipAddress = null, + ?string $userAgent = null ) { $path = "user_management/authenticate"; $params = [ @@ -817,7 +817,7 @@ public function authenticateWithSelectedOrganization( * * @return Resource\AuthenticationResponse */ - public function authenticateWithCode($clientId, $code, $ipAddress = null, $userAgent = null) + public function authenticateWithCode($clientId, $code, ?string $ipAddress = null, ?string $userAgent = null) { $path = "user_management/authenticate"; $params = [ @@ -847,7 +847,7 @@ public function authenticateWithCode($clientId, $code, $ipAddress = null, $userA * * @return Resource\AuthenticationResponse */ - public function authenticateWithEmailVerification($clientId, $code, $pendingAuthenticationToken, $ipAddress = null, $userAgent = null) + public function authenticateWithEmailVerification($clientId, $code, $pendingAuthenticationToken, ?string $ipAddress = null, ?string $userAgent = null) { $path = "user_management/authenticate"; $params = [ @@ -883,8 +883,8 @@ public function authenticateWithMagicAuth( $clientId, $code, $userId, - $ipAddress = null, - $userAgent = null + ?string $ipAddress = null, + ?string $userAgent = null ) { $path = "user_management/authenticate"; $params = [ @@ -917,9 +917,9 @@ public function authenticateWithMagicAuth( public function authenticateWithRefreshToken( $clientId, $refreshToken, - $ipAddress = null, - $userAgent = null, - $organizationId = null + ?string $ipAddress = null, + ?string $userAgent = null, + ?string $organizationId = null ) { $path = "user_management/authenticate"; $params = [ @@ -956,8 +956,8 @@ public function authenticateWithTotp( $pendingAuthenticationToken, $authenticationChallengeId, $code, - $ipAddress = null, - $userAgent = null + ?string $ipAddress = null, + ?string $userAgent = null ) { $path = "user_management/authenticate"; $params = [ @@ -988,7 +988,7 @@ public function authenticateWithTotp( * * @return Resource\AuthenticationFactorAndChallengeTotp */ - public function enrollAuthFactor($userId, $type, $totpIssuer = null, $totpUser = null) + public function enrollAuthFactor($userId, $type, ?string $totpIssuer = null, ?string $totpUser = null) { $path = "user_management/users/{$userId}/auth_factors"; @@ -1234,7 +1234,7 @@ public function getMagicAuth($magicAuthId) */ public function createMagicAuth( $email, - $invitationToken = null + ?string $invitationToken = null ) { $path = "user_management/magic_auth"; @@ -1315,7 +1315,7 @@ public function getJwksUrl(string $clientId) * * @return string */ - public function getLogoutUrl(string $sessionId, string $return_to = null) + public function getLogoutUrl(string $sessionId, ?string $return_to = null) { if (!isset($sessionId) || empty($sessionId)) { throw new Exception\UnexpectedValueException("sessionId must not be empty"); diff --git a/tests/WorkOS/AuditLogsTest.php b/tests/WorkOS/AuditLogsTest.php index 54127fc2..310cfaa4 100644 --- a/tests/WorkOS/AuditLogsTest.php +++ b/tests/WorkOS/AuditLogsTest.php @@ -111,6 +111,37 @@ public function testCreateExport() $this->assertSame($exportFixture, $auditLogExport->toArray()); } + public function testCreateExportWithNullOptionalParams() + { + $path = "audit_logs/exports"; + + $organizationId = "org_123"; + $rangeStart = "2022-08-18T18:07:10.822Z"; + $rangeEnd = "2022-08-18T18:07:10.822Z"; + // The implementation filters out null values, so they won't be in the params array + $params = [ + "organization_id" => $organizationId, + "range_end" => $rangeEnd, + "range_start" => $rangeStart + ]; + + $result = $this->createExportResponseFixture(); + + $this->mockRequest( + Client::METHOD_POST, + $path, + null, + $params, + true, + $result + ); + + $auditLogExport = $this->al->createExport($organizationId, $rangeStart, $rangeEnd, null, null, null, null, null); + $exportFixture = $this->createExportFixture(); + + $this->assertSame($exportFixture, $auditLogExport->toArray()); + } + public function testGetExport() { $auditLogExportId = "123"; diff --git a/tests/WorkOS/ClientTest.php b/tests/WorkOS/ClientTest.php index 3bc6ec77..654ce9be 100644 --- a/tests/WorkOS/ClientTest.php +++ b/tests/WorkOS/ClientTest.php @@ -185,6 +185,46 @@ public function testClientThrowsRequestExceptionsWithErrors($statusCode, $except } } + public function testClientRequestWithNullHeadersAndParams() + { + $this->withApiKeyAndClientId(); + + $path = "some/place"; + $result = json_encode(["data" => "test"]); + + // Client::request always generates base headers even when null is passed + // When $withAuth is false (default), it still includes User-Agent header + $this->mockRequest( + Client::METHOD_GET, + $path, + null, + null, + false, + $result + ); + + $response = Client::request(Client::METHOD_GET, $path, null, null); + $this->assertSame(["data" => "test"], $response); + } + + public function testClientGenerateUrl() + { + $this->withApiKeyAndClientId(); + + $path = "test/path"; + $url = Client::generateUrl($path); + $this->assertStringContainsString($path, $url); + } + + public function testClientGenerateUrlWithNullParams() + { + $this->withApiKeyAndClientId(); + + $path = "test/path"; + $url = Client::generateUrl($path, null); + $this->assertStringContainsString($path, $url); + } + // Providers public static function requestExceptionTestProvider() { diff --git a/tests/WorkOS/UserManagementTest.php b/tests/WorkOS/UserManagementTest.php index cb527135..78164918 100644 --- a/tests/WorkOS/UserManagementTest.php +++ b/tests/WorkOS/UserManagementTest.php @@ -75,6 +75,40 @@ public function testUpdateUser() $this->assertSame($user, $response->toArray()); } + public function testUpdateUserWithNullOptionalParams() + { + $userId = "user_01H7X1M4TZJN5N4HG4XXMA1234"; + $path = "user_management/users/{$userId}"; + + $result = $this->createUserResponseFixture(); + + $params = [ + "first_name" => null, + "last_name" => null, + "email_verified" => null, + "password" => null, + "password_hash" => null, + "password_hash_type" => null, + "external_id" => null, + "metadata" => null, + "email" => null + ]; + + $this->mockRequest( + Client::METHOD_PUT, + $path, + null, + $params, + true, + $result + ); + + $user = $this->userFixture(); + + $response = $this->userManagement->updateUser("user_01H7X1M4TZJN5N4HG4XXMA1234", null, null, null, null, null, null, null, null, null); + $this->assertSame($user, $response->toArray()); + } + public function testAuthorizationURLInvalidInputs() { $this->expectException(Exception\UnexpectedValueException::class); @@ -423,6 +457,35 @@ public function testEnrollAuthFactor() $this->assertSame($enrollUserAuthChallengeFixture, $enrollFactorTotp->authenticationChallenge->toArray()); } + public function testEnrollAuthFactorWithNullOptionalParams() + { + $userId = "user_123456"; + $path = "user_management/users/{$userId}/auth_factors"; + $params = [ + "type" => "totp", + "totp_user" => null, + "totp_issuer" => null + ]; + + $result = $this->enrollAuthFactorResponseFixture(); + + $this->mockRequest( + Client::METHOD_POST, + $path, + null, + $params, + true, + $result + ); + + $enrollFactorTotp = $this->userManagement->enrollAuthFactor($userId, "totp", null, null); + $enrollUserAuthFactorFixture = $this->enrollAuthFactorFixture(); + $enrollUserAuthChallengeFixture = $this->enrollAuthChallengeFixture(); + + $this->assertSame($enrollUserAuthFactorFixture, $enrollFactorTotp->authenticationFactor->toArray()); + $this->assertSame($enrollUserAuthChallengeFixture, $enrollFactorTotp->authenticationChallenge->toArray()); + } + public function testAuthenticateWithRefreshToken() { $path = "user_management/authenticate"; @@ -550,6 +613,39 @@ public function testCreateUser() $this->assertSame($user, $response->toArray()); } + public function testCreateUserWithNullOptionalParams() + { + $path = "user_management/users"; + + $result = $this->createUserResponseFixture(); + + $params = [ + "email" => "test@test.com", + "password" => null, + "first_name" => null, + "last_name" => null, + "email_verified" => null, + "password_hash" => null, + "password_hash_type" => null, + "external_id" => null, + "metadata" => null + ]; + + $this->mockRequest( + Client::METHOD_POST, + $path, + null, + $params, + true, + $result + ); + + $user = $this->userFixture(); + + $response = $this->userManagement->createUser("test@test.com", null, null, null, null, null, null, null, null); + $this->assertSame($user, $response->toArray()); + } + public function testGetEmailVerification() { $emailVerificationId = "email_verification_01E4ZCR3C56J083X43JQXF3JK5"; @@ -1111,10 +1207,16 @@ public function testUpdateOrganizationMembership() $this->assertSame($this->organizationMembershipFixture(), $response->toArray()); } +<<<<<<< HEAD public function testUpdateOrganizationMembershipWithRoleSlugs() { $organizationMembershipId = "om_01E4ZCR3C56J083X43JQXF3JK5"; $roleSlugs = ["admin"]; +======= + public function testUpdateOrganizationMembershipWithNullRoleSlug() + { + $organizationMembershipId = "om_01E4ZCR3C56J083X43JQXF3JK5"; +>>>>>>> 560a945 (Fix PHP 8.4 deprecation: Add explicit nullable type hints) $path = "user_management/organization_memberships/{$organizationMembershipId}"; $result = $this->organizationMembershipResponseFixture(); @@ -1123,16 +1225,27 @@ public function testUpdateOrganizationMembershipWithRoleSlugs() Client::METHOD_PUT, $path, null, +<<<<<<< HEAD ["role_slugs" => $roleSlugs], +======= + ["role_slug" => null], +>>>>>>> 560a945 (Fix PHP 8.4 deprecation: Add explicit nullable type hints) true, $result ); +<<<<<<< HEAD $response = $this->userManagement->updateOrganizationMembership($organizationMembershipId, null, $roleSlugs); $this->assertSame($this->organizationMembershipFixture(), $response->toArray()); } +======= + $response = $this->userManagement->updateOrganizationMembership($organizationMembershipId, null); + $this->assertSame($this->organizationMembershipFixture(), $response->toArray()); + } + +>>>>>>> 560a945 (Fix PHP 8.4 deprecation: Add explicit nullable type hints) public function testDeactivateOrganizationMembership() { $organizationMembershipId = "om_01E4ZCR3C56J083X43JQXF3JK5"; From be83f586776ba7f592ef28ab0b23efa1ae9fbd5c Mon Sep 17 00:00:00 2001 From: Ben Lobaugh Date: Tue, 25 Nov 2025 07:32:58 -0800 Subject: [PATCH 2/2] Resolved conflicts due to missing changes upstream. Take particular note of the changes to $roleSlugs. There was a mismatch between the phpdoc, tests, and api docs. This commit went with the array due to both the test and api doc specifying the array type. The php doc was updated to reflect the array change. --- lib/UserManagement.php | 8 +-- tests/WorkOS/UserManagementTest.php | 97 ++++++++++++++++++++++++----- 2 files changed, 87 insertions(+), 18 deletions(-) diff --git a/lib/UserManagement.php b/lib/UserManagement.php index afb377d1..6045ab9e 100644 --- a/lib/UserManagement.php +++ b/lib/UserManagement.php @@ -222,13 +222,13 @@ public function deleteUser($userId) * @param string $userId User ID * @param string $organizationId Organization ID * @param string|null $roleSlug Role Slug - * @param string|null $roleSlugs Role Slugs + * @param array|null $roleSlugs Role Slugs * * @throws Exception\WorkOSException * * @return Resource\OrganizationMembership */ - public function createOrganizationMembership($userId, $organizationId, ?string $roleSlug = null, ?string $roleSlugs = null) + public function createOrganizationMembership($userId, $organizationId, ?string $roleSlug = null, ?array $roleSlugs = null) { $path = "user_management/organization_memberships"; @@ -309,13 +309,13 @@ public function deleteOrganizationMembership($organizationMembershipId) * * @param string $organizationMembershipId Organization Membership ID * @param string|null $role_slug The unique slug of the role to grant to this membership. - * @param string|null $role_slugs The unique slugs of the roles to grant to this membership. + * @param array|null $role_slugs The unique slugs of the roles to grant to this membership. * * @throws Exception\WorkOSException * * @return Resource\OrganizationMembership */ - public function updateOrganizationMembership($organizationMembershipId, ?string $roleSlug = null, ?string $roleSlugs = null) + public function updateOrganizationMembership($organizationMembershipId, ?string $roleSlug = null, ?array $roleSlugs = null) { $path = "user_management/organization_memberships/{$organizationMembershipId}"; diff --git a/tests/WorkOS/UserManagementTest.php b/tests/WorkOS/UserManagementTest.php index 78164918..9326bedf 100644 --- a/tests/WorkOS/UserManagementTest.php +++ b/tests/WorkOS/UserManagementTest.php @@ -1039,6 +1039,35 @@ public function testCreateOrganizationMembershipWithRoleSlugs() $this->assertSame($organizationMembership, $response->toArray()); } + public function testCreateOrganizationMembershipWithNullRoleParams() + { + $userId = "user_01H7X1M4TZJN5N4HG4XXMA1234"; + $orgId = "org_01EHQMYV6MBK39QC5PZXHY59C3"; + $path = "user_management/organization_memberships"; + + $result = $this->organizationMembershipResponseFixture(); + + // When both roleSlug and roleSlugs are null, neither should be in params + $params = [ + "organization_id" => $orgId, + "user_id" => $userId, + ]; + + $this->mockRequest( + Client::METHOD_POST, + $path, + null, + $params, + true, + $result + ); + + $organizationMembership = $this->organizationMembershipFixture(); + + $response = $this->userManagement->createOrganizationMembership($userId, $orgId, null, null); + $this->assertSame($organizationMembership, $response->toArray()); + } + public function testGetOrganizationMembership() { $organizationMembershipId = "om_01E4ZCR3C56J083X43JQXF3JK5"; @@ -1207,16 +1236,10 @@ public function testUpdateOrganizationMembership() $this->assertSame($this->organizationMembershipFixture(), $response->toArray()); } -<<<<<<< HEAD public function testUpdateOrganizationMembershipWithRoleSlugs() { $organizationMembershipId = "om_01E4ZCR3C56J083X43JQXF3JK5"; $roleSlugs = ["admin"]; -======= - public function testUpdateOrganizationMembershipWithNullRoleSlug() - { - $organizationMembershipId = "om_01E4ZCR3C56J083X43JQXF3JK5"; ->>>>>>> 560a945 (Fix PHP 8.4 deprecation: Add explicit nullable type hints) $path = "user_management/organization_memberships/{$organizationMembershipId}"; $result = $this->organizationMembershipResponseFixture(); @@ -1225,27 +1248,36 @@ public function testUpdateOrganizationMembershipWithNullRoleSlug() Client::METHOD_PUT, $path, null, -<<<<<<< HEAD ["role_slugs" => $roleSlugs], -======= - ["role_slug" => null], ->>>>>>> 560a945 (Fix PHP 8.4 deprecation: Add explicit nullable type hints) true, $result ); -<<<<<<< HEAD $response = $this->userManagement->updateOrganizationMembership($organizationMembershipId, null, $roleSlugs); $this->assertSame($this->organizationMembershipFixture(), $response->toArray()); } + public function testUpdateOrganizationMembershipWithNullRoleParams() + { + $organizationMembershipId = "om_01E4ZCR3C56J083X43JQXF3JK5"; + $path = "user_management/organization_memberships/{$organizationMembershipId}"; + + $result = $this->organizationMembershipResponseFixture(); + + // When both roleSlug and roleSlugs are null, params should be empty array + $this->mockRequest( + Client::METHOD_PUT, + $path, + null, + [], + true, + $result + ); -======= - $response = $this->userManagement->updateOrganizationMembership($organizationMembershipId, null); + $response = $this->userManagement->updateOrganizationMembership($organizationMembershipId, null, null); $this->assertSame($this->organizationMembershipFixture(), $response->toArray()); } ->>>>>>> 560a945 (Fix PHP 8.4 deprecation: Add explicit nullable type hints) public function testDeactivateOrganizationMembership() { $organizationMembershipId = "om_01E4ZCR3C56J083X43JQXF3JK5"; @@ -1328,6 +1360,43 @@ public function testSendInvitation() $this->assertSame($response->toArray(), $expected); } + public function testSendInvitationWithNullOptionalParams() + { + $path = "user_management/invitations"; + + $result = $this->invitationResponseFixture(); + + // The implementation includes null values in params + $params = [ + "email" => "someemail@test.com", + "organization_id" => null, + "expires_in_days" => null, + "inviter_user_id" => null, + "role_slug" => null + ]; + + $this->mockRequest( + Client::METHOD_POST, + $path, + null, + $params, + true, + $result + ); + + $response = $this->userManagement->sendInvitation( + "someemail@test.com", + null, + null, + null, + null + ); + + $expected = $this->invitationFixture(); + + $this->assertSame($response->toArray(), $expected); + } + public function testGetInvitation() { $invitationId = "invitation_01E4ZCR3C56J083X43JQXF3JK5";