diff --git a/lib/DirectorySync.php b/lib/DirectorySync.php index a065880..003f2af 100644 --- a/lib/DirectorySync.php +++ b/lib/DirectorySync.php @@ -24,7 +24,8 @@ class DirectorySync * * @throws Exception\WorkOSException * - * @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 + * @return Resource\PaginatedResource A paginated resource containing before/after cursors and directories array. + * Supports: [$before, $after, $directories] = $result, ["directories" => $dirs] = $result, $result->directories */ public function listDirectories( ?string $domain = null, @@ -54,13 +55,7 @@ public function listDirectories( true ); - $directories = []; - list($before, $after) = Util\Request::parsePaginationArgs($response); - foreach ($response["data"] as $responseData) { - \array_push($directories, Resource\Directory::constructFromResponse($responseData)); - } - - return [$before, $after, $directories]; + return Resource\PaginatedResource::constructFromResponse($response, Resource\Directory::class, 'directories'); } /** @@ -75,7 +70,8 @@ public function listDirectories( * * @throws Exception\WorkOSException * - * @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 + * @return Resource\PaginatedResource A paginated resource containing before/after cursors and groups array. + * Supports: [$before, $after, $groups] = $result, ["groups" => $groups] = $result, $result->groups */ public function listGroups( ?string $directory = null, @@ -108,13 +104,7 @@ public function listGroups( true ); - $groups = []; - list($before, $after) = Util\Request::parsePaginationArgs($response); - foreach ($response["data"] as $response) { - \array_push($groups, Resource\DirectoryGroup::constructFromResponse($response)); - } - - return [$before, $after, $groups]; + return Resource\PaginatedResource::constructFromResponse($response, Resource\DirectoryGroup::class, 'groups'); } /** @@ -151,7 +141,8 @@ public function getGroup($directoryGroup) * @param null|string $after Directory User ID to look after * @param Resource\Order $order The Order in which to paginate records * - * @return array{?string, ?string, Resource\DirectoryUser[]} An array containing the Directory User ID to use as before and after cursor, and an array of Directory User instances + * @return Resource\PaginatedResource A paginated resource containing before/after cursors and users array. + * Supports: [$before, $after, $users] = $result, ["users" => $users] = $result, $result->users * * @throws Exception\WorkOSException */ @@ -186,13 +177,7 @@ public function listUsers( true ); - $users = []; - list($before, $after) = Util\Request::parsePaginationArgs($response); - foreach ($response["data"] as $response) { - \array_push($users, Resource\DirectoryUser::constructFromResponse($response)); - } - - return [$before, $after, $users]; + return Resource\PaginatedResource::constructFromResponse($response, Resource\DirectoryUser::class, 'users'); } /** diff --git a/lib/Organizations.php b/lib/Organizations.php index efd2a6b..a0058db 100644 --- a/lib/Organizations.php +++ b/lib/Organizations.php @@ -21,7 +21,8 @@ class Organizations * @param null|string $after Organization ID to look after * @param Resource\Order $order The Order in which to paginate records * - * @return array{?string, ?string, Resource\Organization[]} An array containing the Organization ID to use as before and after cursor, and an array of Organization instances + * @return Resource\PaginatedResource A paginated resource containing before/after cursors and organizations array. + * Supports: [$before, $after, $organizations] = $result, ["organizations" => $orgs] = $result, $result->organizations * * @throws Exception\WorkOSException */ @@ -49,13 +50,7 @@ public function listOrganizations( true ); - $organizations = []; - list($before, $after) = Util\Request::parsePaginationArgs($response); - foreach ($response["data"] as $responseData) { - \array_push($organizations, Resource\Organization::constructFromResponse($responseData)); - } - - return [$before, $after, $organizations]; + return Resource\PaginatedResource::constructFromResponse($response, Resource\Organization::class, 'organizations'); } /** @@ -262,7 +257,8 @@ public function listOrganizationRoles($organizationId) * * @throws Exception\WorkOSException * - * @return array{?string, ?string, Resource\FeatureFlag[]} An array containing the FeatureFlag ID to use as before and after cursor, and an array of FeatureFlag instances + * @return Resource\PaginatedResource A paginated resource containing before/after cursors and feature_flags array. + * Supports: [$before, $after, $flags] = $result, ["feature_flags" => $flags] = $result, $result->feature_flags */ public function listOrganizationFeatureFlags( $organizationId, @@ -287,12 +283,6 @@ public function listOrganizationFeatureFlags( true ); - $featureFlags = []; - list($before, $after) = Util\Request::parsePaginationArgs($response); - foreach ($response["data"] as $responseData) { - \array_push($featureFlags, Resource\FeatureFlag::constructFromResponse($responseData)); - } - - return [$before, $after, $featureFlags]; + return Resource\PaginatedResource::constructFromResponse($response, Resource\FeatureFlag::class, 'feature_flags'); } } diff --git a/lib/Resource/PaginatedResource.php b/lib/Resource/PaginatedResource.php new file mode 100644 index 0000000..7cc161c --- /dev/null +++ b/lib/Resource/PaginatedResource.php @@ -0,0 +1,224 @@ + $users, "after" => $after] = $result + * 3. Fluent property access: $result->users, $result->after, $result->before + * + * This class standardizes pagination across all WorkOS resources while maintaining + * backwards compatibility with existing code. + */ +class PaginatedResource implements \ArrayAccess, \IteratorAggregate, \Countable +{ + /** + * @var ?string Before cursor for pagination + */ + private $before; + + /** + * @var ?string After cursor for pagination + */ + private $after; + + /** + * @var array The paginated data items + */ + private $data; + + /** + * @var string The key name for the data array (e.g., 'users', 'directories') + */ + private $dataKey; + + /** + * PaginatedResource constructor. + * + * @param ?string $before Before cursor + * @param ?string $after After cursor + * @param array $data Array of resource objects + * @param string $dataKey The key name for accessing the data + */ + public function __construct(?string $before, ?string $after, array $data, string $dataKey) + { + if (in_array($dataKey, ['before', 'after', 'data'], true)) { + throw new \InvalidArgumentException("dataKey '$dataKey' conflicts with reserved keys"); + } + + $this->before = $before; + $this->after = $after; + $this->data = $data; + $this->dataKey = $dataKey; + } + + /** + * Construct a PaginatedResource from an API response + * + * @param array $response The API response containing 'data', 'list_metadata', etc. + * @param string $resourceClass The fully qualified class name of the resource type + * @param string $dataKey The key name for the data array (e.g., 'users', 'directories') + * @return self + */ + public static function constructFromResponse(array $response, string $resourceClass, string $dataKey): self + { + $data = []; + list($before, $after) = \WorkOS\Util\Request::parsePaginationArgs($response); + + foreach ($response["data"] as $responseData) { + \array_push($data, $resourceClass::constructFromResponse($responseData)); + } + + return new self($before, $after, $data, $dataKey); + } + + /** + * Magic getter for fluent property access + * + * @param string $name Property name + * @return mixed + */ + public function __get(string $name) + { + if ($name === 'before') { + return $this->before; + } + + if ($name === 'after') { + return $this->after; + } + + if ($name === 'data' || $name === $this->dataKey) { + return $this->data; + } + + return null; + } + + /** + * ArrayAccess: Check if offset exists + * + * @param mixed $offset + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset): bool + { + // Support numeric indices for bare destructuring + if (is_int($offset)) { + return $offset >= 0 && $offset <= 2; + } + + // Support named keys for named destructuring + return in_array($offset, ['before', 'after', 'data', $this->dataKey], true); + } + + /** + * ArrayAccess: Get value at offset + * + * @param mixed $offset + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + // Support numeric indices for bare destructuring: [0 => before, 1 => after, 2 => data] + if ($offset === 0) { + return $this->before; + } + + if ($offset === 1) { + return $this->after; + } + + if ($offset === 2) { + return $this->data; + } + + // Support named keys for named destructuring + if ($offset === 'before') { + return $this->before; + } + + if ($offset === 'after') { + return $this->after; + } + + if ($offset === 'data' || $offset === $this->dataKey) { + return $this->data; + } + + return null; + } + + /** + * ArrayAccess: Set value at offset (not supported) + * + * @param mixed $offset + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value): void + { + throw new \BadMethodCallException('PaginatedResource is immutable'); + } + + /** + * ArrayAccess: Unset offset (not supported) + * + * @param mixed $offset + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset): void + { + throw new \BadMethodCallException('PaginatedResource is immutable'); + } + + /** + * IteratorAggregate: Get iterator for the data array + * + * @return \ArrayIterator + */ + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->data); + } + + /** + * Magic isset for property checking + * + * @param string $name + * @return bool + */ + public function __isset(string $name): bool + { + if ($name === 'before') { + return $this->before !== null; + } + + if ($name === 'after') { + return $this->after !== null; + } + + if ($name === 'data' || $name === $this->dataKey) { + return true; + } + + return false; + } + + /** + * Countable: Get the number of data items + * + * @return int + */ + public function count(): int + { + return count($this->data); + } +} diff --git a/lib/SSO.php b/lib/SSO.php index dcc57a4..02d8535 100644 --- a/lib/SSO.php +++ b/lib/SSO.php @@ -214,7 +214,8 @@ public function getConnection($connection) * @param null|string $after Connection ID to look after * @param Resource\Order $order The Order in which to paginate records * - * @return array{?string, ?string, Resource\Connection[]} An array containing the Directory Connection ID to use as before and after cursor, and an array of Connection instances + * @return Resource\PaginatedResource A paginated resource containing before/after cursors and connections array. + * Supports: [$before, $after, $connections] = $result, ["connections" => $connections] = $result, $result->connections * * @throws Exception\WorkOSException */ @@ -246,12 +247,6 @@ public function listConnections( true ); - $connections = []; - list($before, $after) = Util\Request::parsePaginationArgs($response); - foreach ($response["data"] as $responseData) { - \array_push($connections, Resource\Connection::constructFromResponse($responseData)); - } - - return [$before, $after, $connections]; + return Resource\PaginatedResource::constructFromResponse($response, Resource\Connection::class, 'connections'); } } diff --git a/lib/UserManagement.php b/lib/UserManagement.php index b0bc7a7..e249cfe 100644 --- a/lib/UserManagement.php +++ b/lib/UserManagement.php @@ -8,13 +8,18 @@ class UserManagement { public const DEFAULT_PAGE_SIZE = 10; + public const DEFAULT_TOKEN_EXPIRATION = 1440; - public const AUTHORIZATION_PROVIDER_AUTHKIT = "authkit"; - public const AUTHORIZATION_PROVIDER_APPLE_OAUTH = "AppleOAuth"; - public const AUTHORIZATION_PROVIDER_GITHUB_OAUTH = "GitHubOAuth"; - public const AUTHORIZATION_PROVIDER_GOOGLE_OAUTH = "GoogleOAuth"; - public const AUTHORIZATION_PROVIDER_MICROSOFT_OAUTH = "MicrosoftOAuth"; + public const AUTHORIZATION_PROVIDER_AUTHKIT = 'authkit'; + + public const AUTHORIZATION_PROVIDER_APPLE_OAUTH = 'AppleOAuth'; + + public const AUTHORIZATION_PROVIDER_GITHUB_OAUTH = 'GitHubOAuth'; + + public const AUTHORIZATION_PROVIDER_GOOGLE_OAUTH = 'GoogleOAuth'; + + public const AUTHORIZATION_PROVIDER_MICROSOFT_OAUTH = 'MicrosoftOAuth'; /** * @var Session\SessionEncryptionInterface|null @@ -56,21 +61,19 @@ private function getSessionEncryptor(): Session\SessionEncryptionInterface /** * Create User. * - * @param string $email The email address of the user. - * @param string|null $password The password of the user. - * @param string|null $firstName The first name of the user. - * @param string|null $lastName The last name of the user. - * @param boolean|null $emailVerified A boolean declaring if the user's email has been verified. - * @param string|null $passwordHash The hashed password to set for the user. - * @param string|null $passwordHashType The algorithm originally used to hash the password. Valid values are `bcrypt`, `ssha`, and `firebase-scrypt`. - * @param string|null $externalId The user's external ID. - * @param array $metadata The user's metadata. + * @param string $email The email address of the user. + * @param string|null $password The password of the user. + * @param string|null $firstName The first name of the user. + * @param string|null $lastName The last name of the user. + * @param bool|null $emailVerified A boolean declaring if the user's email has been verified. + * @param string|null $passwordHash The hashed password to set for the user. + * @param string|null $passwordHashType The algorithm originally used to hash the password. Valid values are `bcrypt`, `ssha`, and `firebase-scrypt`. + * @param string|null $externalId The user's external ID. + * @param array $metadata The user's metadata. + * @return Resource\User * * @throws Exception\WorkOSException - * - * @return Resource\User */ - public function createUser( $email, ?string $password = null, @@ -82,17 +85,17 @@ public function createUser( ?string $externalId = null, ?array $metadata = null ) { - $path = "user_management/users"; + $path = 'user_management/users'; $params = [ - "email" => $email, - "password" => $password, - "first_name" => $firstName, - "last_name" => $lastName, - "email_verified" => $emailVerified, - "password_hash" => $passwordHash, - "password_hash_type" => $passwordHashType, - "external_id" => $externalId, - "metadata" => $metadata + 'email' => $email, + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName, + 'email_verified' => $emailVerified, + 'password_hash' => $passwordHash, + 'password_hash_type' => $passwordHashType, + 'external_id' => $externalId, + 'metadata' => $metadata, ]; $response = Client::request(Client::METHOD_POST, $path, null, $params, true); @@ -103,11 +106,10 @@ public function createUser( /** * Get a User. * - * @param string $userId user ID + * @param string $userId user ID + * @return Resource\User * * @throws Exception\WorkOSException - * - * @return Resource\User */ public function getUser($userId) { @@ -121,11 +123,10 @@ public function getUser($userId) /** * Get a User by external ID. * - * @param string $externalId The external ID of the user. + * @param string $externalId The external ID of the user. + * @return Resource\User * * @throws Exception\WorkOSException - * - * @return Resource\User */ public function getUserByExternalId($externalId) { @@ -139,20 +140,19 @@ public function getUserByExternalId($externalId) /** * Update a User * - * @param string $userId The unique ID of the user. - * @param string|null $firstName The first name of the user. - * @param string|null $lastName The last name of the user. - * @param boolean|null $emailVerified A boolean declaring if the user's email has been verified. - * @param string|null $password The password to set for the user. - * @param string|null $passwordHash The hashed password to set for the user. - * @param string|null $passwordHashType The algorithm originally used to hash the password. Valid values are `bcrypt`, `ssha`, and `firebase-scrypt`. - * @param string|null $externalId The user's external ID. - * @param array|null $metadata The user's metadata. - * @param string|null $email The email address of the user. + * @param string $userId The unique ID of the user. + * @param string|null $firstName The first name of the user. + * @param string|null $lastName The last name of the user. + * @param bool|null $emailVerified A boolean declaring if the user's email has been verified. + * @param string|null $password The password to set for the user. + * @param string|null $passwordHash The hashed password to set for the user. + * @param string|null $passwordHashType The algorithm originally used to hash the password. Valid values are `bcrypt`, `ssha`, and `firebase-scrypt`. + * @param string|null $externalId The user's external ID. + * @param array|null $metadata The user's metadata. + * @param string|null $email The email address of the user. + * @return Resource\User * * @throws Exception\WorkOSException - * - * @return Resource\User */ public function updateUser( $userId, @@ -169,15 +169,15 @@ public function updateUser( $path = "user_management/users/{$userId}"; $params = [ - "first_name" => $firstName, - "last_name" => $lastName, - "email_verified" => $emailVerified, - "password" => $password, - "password_hash" => $passwordHash, - "password_hash_type" => $passwordHashType, - "external_id" => $externalId, - "metadata" => $metadata, - "email" => $email + 'first_name' => $firstName, + 'last_name' => $lastName, + 'email_verified' => $emailVerified, + 'password' => $password, + 'password_hash' => $passwordHash, + 'password_hash_type' => $passwordHashType, + 'external_id' => $externalId, + 'metadata' => $metadata, + 'email' => $email, ]; $response = Client::request(Client::METHOD_PUT, $path, null, $params, true); @@ -188,14 +188,13 @@ public function updateUser( /** * List Users. * - * @param null|string $email - * @param null|string $organizationId Organization users are a member of - * @param int $limit Maximum number of records to return - * @param null|string $before User ID to look before - * @param null|string $after User ID to look after - * @param Resource\Order $order The Order in which to paginate records - * - * @return array{?string, ?string, Resource\User[]} An array containing the Directory User ID to use as before and after cursor, and an array of User instances + * @param null|string $organizationId Organization users are a member of + * @param int $limit Maximum number of records to return + * @param null|string $before User ID to look before + * @param null|string $after User ID to look after + * @param Resource\Order $order The Order in which to paginate records + * @return Resource\PaginatedResource A paginated resource containing before/after cursors and users array. + * Supports: [$before, $after, $users] = $result, ["users" => $users] = $result, $result->users * * @throws Exception\WorkOSException */ @@ -207,15 +206,15 @@ public function listUsers( ?string $after = null, ?string $order = null ) { - $path = "user_management/users"; + $path = 'user_management/users'; $params = [ - "email" => $email, - "organization_id" => $organizationId, - "limit" => $limit, - "before" => $before, - "after" => $after, - "order" => $order + 'email' => $email, + 'organization_id' => $organizationId, + 'limit' => $limit, + 'before' => $before, + 'after' => $after, + 'order' => $order, ]; $response = Client::request( @@ -226,23 +225,16 @@ public function listUsers( true ); - $users = []; - list($before, $after) = Util\Request::parsePaginationArgs($response); - foreach ($response["data"] as $responseData) { - \array_push($users, Resource\User::constructFromResponse($responseData)); - } - - return [$before, $after, $users]; + return Resource\PaginatedResource::constructFromResponse($response, Resource\User::class, 'users'); } /** * Delete a user. * - * @param string $userId Unique ID of a user + * @param string $userId Unique ID of a user + * @return Resource\Response * * @throws Exception\WorkOSException - * - * @return Resource\Response */ public function deleteUser($userId) { @@ -256,30 +248,29 @@ public function deleteUser($userId) /** * Add a User to an Organization. * - * @param string $userId User ID - * @param string $organizationId Organization ID - * @param string|null $roleSlug Role Slug - * @param array|null $roleSlugs Role Slugs + * @param string $userId User ID + * @param string $organizationId Organization ID + * @param string|null $roleSlug Role Slug + * @param array|null $roleSlugs Role Slugs + * @return Resource\OrganizationMembership * * @throws Exception\WorkOSException - * - * @return Resource\OrganizationMembership */ public function createOrganizationMembership($userId, $organizationId, ?string $roleSlug = null, ?array $roleSlugs = null) { - $path = "user_management/organization_memberships"; + $path = 'user_management/organization_memberships'; $params = [ - "organization_id" => $organizationId, - "user_id" => $userId + 'organization_id' => $organizationId, + 'user_id' => $userId, ]; - if (!is_null($roleSlug)) { - $params["role_slug"] = $roleSlug; + if (! is_null($roleSlug)) { + $params['role_slug'] = $roleSlug; } - if (!is_null($roleSlugs)) { - $params["role_slugs"] = $roleSlugs; + if (! is_null($roleSlugs)) { + $params['role_slugs'] = $roleSlugs; } $response = Client::request( @@ -296,11 +287,10 @@ public function createOrganizationMembership($userId, $organizationId, ?string $ /** * Get an Organization Membership. * - * @param string $organizationMembershipId Organization Membership ID + * @param string $organizationMembershipId Organization Membership ID + * @return Resource\OrganizationMembership * * @throws Exception\WorkOSException - * - * @return Resource\OrganizationMembership */ public function getOrganizationMembership($organizationMembershipId) { @@ -320,11 +310,10 @@ public function getOrganizationMembership($organizationMembershipId) /** * Remove a user from an organization. * - * @param string $organizationMembershipId Organization Membership ID + * @param string $organizationMembershipId Organization Membership ID + * @return Resource\Response * * @throws Exception\WorkOSException - * - * @return Resource\Response */ public function deleteOrganizationMembership($organizationMembershipId) { @@ -344,13 +333,12 @@ public function deleteOrganizationMembership($organizationMembershipId) /** * Update a User organization membership. * - * @param string $organizationMembershipId Organization Membership ID - * @param string|null $role_slug The unique slug of the role to grant to this membership. - * @param array|null $role_slugs The unique slugs of the roles to grant to this membership. + * @param string $organizationMembershipId Organization Membership ID + * @param string|null $role_slug The unique slug of the role to grant to this membership. + * @param array|null $role_slugs The unique slugs of the roles to grant to this membership. + * @return Resource\OrganizationMembership * * @throws Exception\WorkOSException - * - * @return Resource\OrganizationMembership */ public function updateOrganizationMembership($organizationMembershipId, ?string $roleSlug = null, ?array $roleSlugs = null) { @@ -358,12 +346,12 @@ public function updateOrganizationMembership($organizationMembershipId, ?string $params = []; - if (!is_null($roleSlug)) { - $params["role_slug"] = $roleSlug; + if (! is_null($roleSlug)) { + $params['role_slug'] = $roleSlug; } - if (!is_null($roleSlugs)) { - $params["role_slugs"] = $roleSlugs; + if (! is_null($roleSlugs)) { + $params['role_slugs'] = $roleSlugs; } $response = Client::request( @@ -380,17 +368,17 @@ public function updateOrganizationMembership($organizationMembershipId, ?string /** * List organization memberships. * - * @param string|null $userId User ID - * @param string|null $organizationId Organization ID - * @param array|null $statuses Organization Membership statuses to filter - * @param int $limit Maximum number of records to return - * @param string|null $before Organization Membership ID to look before - * @param string|null $after Organization Membership ID to look after - * @param Resource\Order $order The Order in which to paginate records + * @param string|null $userId User ID + * @param string|null $organizationId Organization ID + * @param array|null $statuses Organization Membership statuses to filter + * @param int $limit Maximum number of records to return + * @param string|null $before Organization Membership ID to look before + * @param string|null $after Organization Membership ID to look after + * @param Resource\Order $order The Order in which to paginate records + * @return Resource\PaginatedResource A paginated resource containing before/after cursors and organization_memberships array. + * Supports: [$before, $after, $memberships] = $result, ["organization_memberships" => $m] = $result, $result->organization_memberships * * @throws Exception\WorkOSException - * - * @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( ?string $userId = null, @@ -401,25 +389,25 @@ public function listOrganizationMemberships( ?string $after = null, ?string $order = null ) { - $path = "user_management/organization_memberships"; + $path = 'user_management/organization_memberships'; if (isset($statuses)) { - if (!is_array($statuses)) { - $msg = "Invalid argument: statuses must be an array or null."; + if (! is_array($statuses)) { + $msg = 'Invalid argument: statuses must be an array or null.'; throw new Exception\UnexpectedValueException($msg); } - $statuses = join(",", $statuses); + $statuses = implode(',', $statuses); } $params = [ - "organization_id" => $organizationId, - "user_id" => $userId, - "statuses" => $statuses, - "limit" => $limit, - "before" => $before, - "after" => $after, - "order" => $order + 'organization_id' => $organizationId, + 'user_id' => $userId, + 'statuses' => $statuses, + 'limit' => $limit, + 'before' => $before, + 'after' => $after, + 'order' => $order, ]; $response = Client::request( @@ -430,25 +418,16 @@ public function listOrganizationMemberships( true ); - $organizationMemberships = []; - - foreach ($response["data"] as $responseData) { - \array_push($organizationMemberships, Resource\OrganizationMembership::constructFromResponse($responseData)); - } - - list($before, $after) = Util\Request::parsePaginationArgs($response); - - return [$before, $after, $organizationMemberships]; + return Resource\PaginatedResource::constructFromResponse($response, Resource\OrganizationMembership::class, 'organization_memberships'); } /** * Deactivate an Organization Membership. * - * @param string $organizationMembershipId Organization Membership ID + * @param string $organizationMembershipId Organization Membership ID + * @return Resource\OrganizationMembership * * @throws Exception\WorkOSException - * - * @return Resource\OrganizationMembership */ public function deactivateOrganizationMembership($organizationMembershipId) { @@ -468,11 +447,10 @@ public function deactivateOrganizationMembership($organizationMembershipId) /** * Reactivate an Organization Membership. * - * @param string $organizationMembershipId Organization Membership ID + * @param string $organizationMembershipId Organization Membership ID + * @return Resource\OrganizationMembership * * @throws Exception\WorkOSException - * - * @return Resource\OrganizationMembership */ public function reactivateOrganizationMembership($organizationMembershipId) { @@ -492,15 +470,14 @@ public function reactivateOrganizationMembership($organizationMembershipId) /** * Sends an Invitation * - * @param string $email The email address of the invitee - * @param string|null $organizationId Organization ID - * @param int|null $expiresInDays expiration delay in days - * @param string|null $inviterUserId User ID of the inviter - * @param string|null $roleSlug Slug of the role to assign to the invitee User + * @param string $email The email address of the invitee + * @param string|null $organizationId Organization ID + * @param int|null $expiresInDays expiration delay in days + * @param string|null $inviterUserId User ID of the inviter + * @param string|null $roleSlug Slug of the role to assign to the invitee User + * @return Resource\Invitation * * @throws Exception\WorkOSException - * - * @return Resource\Invitation */ public function sendInvitation( $email, @@ -509,14 +486,14 @@ public function sendInvitation( ?string $inviterUserId = null, ?string $roleSlug = null ) { - $path = "user_management/invitations"; + $path = 'user_management/invitations'; $params = [ - "email" => $email, - "organization_id" => $organizationId, - "expires_in_days" => $expiresInDays, - "inviter_user_id" => $inviterUserId, - "role_slug" => $roleSlug + 'email' => $email, + 'organization_id' => $organizationId, + 'expires_in_days' => $expiresInDays, + 'inviter_user_id' => $inviterUserId, + 'role_slug' => $roleSlug, ]; $response = Client::request( @@ -533,11 +510,10 @@ public function sendInvitation( /** * Get an Invitation * - * @param string $invitationId ID of the Invitation + * @param string $invitationId ID of the Invitation + * @return Resource\Invitation * * @throws Exception\WorkOSException - * - * @return Resource\Invitation */ public function getInvitation($invitationId) { @@ -557,11 +533,10 @@ public function getInvitation($invitationId) /** * Find an Invitation by Token * - * @param string $invitationToken The token of the Invitation + * @param string $invitationToken The token of the Invitation + * @return Resource\Invitation * * @throws Exception\WorkOSException - * - * @return Resource\Invitation */ public function findInvitationByToken($invitationToken) { @@ -581,16 +556,16 @@ public function findInvitationByToken($invitationToken) /** * List Invitations * - * @param string|null $email Email of the invitee - * @param string|null $organizationId Organization ID - * @param int $limit Maximum number of records to return - * @param string|null $before Organization Membership ID to look before - * @param string|null $after Organization Membership ID to look after - * @param Resource\Order $order The Order in which to paginate records + * @param string|null $email Email of the invitee + * @param string|null $organizationId Organization ID + * @param int $limit Maximum number of records to return + * @param string|null $before Organization Membership ID to look before + * @param string|null $after Organization Membership ID to look after + * @param Resource\Order $order The Order in which to paginate records + * @return Resource\PaginatedResource A paginated resource containing before/after cursors and invitations array. + * Supports: [$before, $after, $invitations] = $result, ["invitations" => $invitations] = $result, $result->invitations * * @throws Exception\WorkOSException - * - * @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( ?string $email = null, @@ -600,15 +575,15 @@ public function listInvitations( ?string $after = null, ?string $order = null ) { - $path = "user_management/invitations"; + $path = 'user_management/invitations'; $params = [ - "email" => $email, - "organization_id" => $organizationId, - "limit" => $limit, - "before" => $before, - "after" => $after, - "order" => $order + 'email' => $email, + 'organization_id' => $organizationId, + 'limit' => $limit, + 'before' => $before, + 'after' => $after, + 'order' => $order, ]; $response = Client::request( @@ -619,25 +594,16 @@ public function listInvitations( true ); - $invitations = []; - - foreach ($response["data"] as $responseData) { - \array_push($invitations, Resource\Invitation::constructFromResponse($responseData)); - } - - list($before, $after) = Util\Request::parsePaginationArgs($response); - - return [$before, $after, $invitations]; + return Resource\PaginatedResource::constructFromResponse($response, Resource\Invitation::class, 'invitations'); } /** * Revoke an Invitation * - * @param string $invitationId ID of the Invitation + * @param string $invitationId ID of the Invitation + * @return Resource\Invitation * * @throws Exception\WorkOSException - * - * @return Resource\Invitation */ public function revokeInvitation($invitationId) { @@ -657,11 +623,10 @@ public function revokeInvitation($invitationId) /** * Resend an Invitation * - * @param string $invitationId ID of the Invitation + * @param string $invitationId ID of the Invitation + * @return Resource\Invitation * * @throws Exception\WorkOSException - * - * @return Resource\Invitation */ public function resendInvitation($invitationId) { @@ -681,20 +646,19 @@ public function resendInvitation($invitationId) /** * Generates an OAuth 2.0 authorization URL used to initiate the SSO flow with WorkOS. * - * @param string $redirectUri URI to direct the user to upon successful completion of SSO - * @param null|array $state Associative array containing state that will be returned from WorkOS as a json encoded string - * @param null|string $provider Service provider that handles the identity of the user - * @param null|string $connectionId Unique identifier for a WorkOS Connection - * @param null|string $organizationId Unique identifier for a WorkOS Organization - * @param null|string $domainHint Domain hint that will be passed as a parameter to the IdP login page - * @param null|string $loginHint Username/email hint that will be passed as a parameter to the to IdP login page - * @param null|string $screenHint The page that the user will be redirected to when the provider is authkit - * @param null|array $providerScopes An array of provider-specific scopes + * @param string $redirectUri URI to direct the user to upon successful completion of SSO + * @param null|array $state Associative array containing state that will be returned from WorkOS as a json encoded string + * @param null|string $provider Service provider that handles the identity of the user + * @param null|string $connectionId Unique identifier for a WorkOS Connection + * @param null|string $organizationId Unique identifier for a WorkOS Organization + * @param null|string $domainHint Domain hint that will be passed as a parameter to the IdP login page + * @param null|string $loginHint Username/email hint that will be passed as a parameter to the to IdP login page + * @param null|string $screenHint The page that the user will be redirected to when the provider is authkit + * @param null|array $providerScopes An array of provider-specific scopes + * @return string * * @throws Exception\UnexpectedValueException * @throws Exception\ConfigurationException - * - * @return string */ public function getAuthorizationUrl( $redirectUri, @@ -707,10 +671,10 @@ public function getAuthorizationUrl( ?string $screenHint = null, ?array $providerScopes = null ) { - $path = "user_management/authorize"; + $path = 'user_management/authorize'; - if (!isset($provider) && !isset($connectionId) && !isset($organizationId)) { - $msg = "Either \$provider, \$connectionId, or \$organizationId is required"; + if (! isset($provider) && ! isset($connectionId) && ! isset($organizationId)) { + $msg = 'Either $provider, $connectionId, or $organizationId is required'; throw new Exception\UnexpectedValueException($msg); } @@ -719,56 +683,56 @@ public function getAuthorizationUrl( self::AUTHORIZATION_PROVIDER_APPLE_OAUTH, self::AUTHORIZATION_PROVIDER_GITHUB_OAUTH, self::AUTHORIZATION_PROVIDER_GOOGLE_OAUTH, - self::AUTHORIZATION_PROVIDER_MICROSOFT_OAUTH + self::AUTHORIZATION_PROVIDER_MICROSOFT_OAUTH, ]; - if (isset($provider) && !\in_array($provider, $supportedProviders)) { - $msg = "Only " . implode("','", $supportedProviders) . " providers are supported"; + if (isset($provider) && ! \in_array($provider, $supportedProviders)) { + $msg = 'Only '.implode("','", $supportedProviders).' providers are supported'; throw new Exception\UnexpectedValueException($msg); } $params = [ - "client_id" => WorkOS::getClientId(), - "response_type" => "code" + 'client_id' => WorkOS::getClientId(), + 'response_type' => 'code', ]; if ($redirectUri) { - $params["redirect_uri"] = $redirectUri; + $params['redirect_uri'] = $redirectUri; } - if (null !== $state && !empty($state)) { - $params["state"] = \json_encode($state); + if ($state !== null && ! empty($state)) { + $params['state'] = \json_encode($state); } if ($provider) { - $params["provider"] = $provider; + $params['provider'] = $provider; } if ($connectionId) { - $params["connection_id"] = $connectionId; + $params['connection_id'] = $connectionId; } if ($organizationId) { - $params["organization_id"] = $organizationId; + $params['organization_id'] = $organizationId; } if ($domainHint) { - $params["domain_hint"] = $domainHint; + $params['domain_hint'] = $domainHint; } if ($loginHint) { - $params["login_hint"] = $loginHint; + $params['login_hint'] = $loginHint; } if ($screenHint !== null) { if ($provider !== self::AUTHORIZATION_PROVIDER_AUTHKIT) { throw new Exception\UnexpectedValueException("A 'screenHint' can only be provided when the provider is 'authkit'."); } - $params["screen_hint"] = $screenHint; + $params['screen_hint'] = $screenHint; } if ($providerScopes && is_array($providerScopes)) { - $params["provider_scopes"] = implode(",", $providerScopes); + $params['provider_scopes'] = implode(',', $providerScopes); } return Client::generateUrl($path, $params); @@ -777,27 +741,26 @@ public function getAuthorizationUrl( /** * Authenticate a User with Password * - * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. - * @param string $email The email address of the user. - * @param string $password The password of the user. - * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. - * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. + * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. + * @param string $email The email address of the user. + * @param string $password The password of the user. + * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. + * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. + * @return Resource\AuthenticationResponse * * @throws Exception\WorkOSException - * - * @return Resource\AuthenticationResponse */ public function authenticateWithPassword($clientId, $email, $password, ?string $ipAddress = null, ?string $userAgent = null) { - $path = "user_management/authenticate"; + $path = 'user_management/authenticate'; $params = [ - "client_id" => $clientId, - "email" => $email, - "password" => $password, - "ip_address" => $ipAddress, - "user_agent" => $userAgent, - "grant_type" => "password", - "client_secret" => WorkOS::getApiKey() + 'client_id' => $clientId, + 'email' => $email, + 'password' => $password, + 'ip_address' => $ipAddress, + 'user_agent' => $userAgent, + 'grant_type' => 'password', + 'client_secret' => WorkOS::getApiKey(), ]; $response = Client::request(Client::METHOD_POST, $path, null, $params, true); @@ -808,15 +771,14 @@ public function authenticateWithPassword($clientId, $email, $password, ?string $ /** * Authenticate a User with Selected Organization * - * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. - * @param string $pendingAuthenticationToken Token returned from a failed authentication attempt due to organization selection being required. - * @param string $organizationId The Organization ID the user selected. - * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. - * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. + * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. + * @param string $pendingAuthenticationToken Token returned from a failed authentication attempt due to organization selection being required. + * @param string $organizationId The Organization ID the user selected. + * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. + * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. + * @return Resource\AuthenticationResponse * * @throws Exception\WorkOSException - * - * @return Resource\AuthenticationResponse */ public function authenticateWithSelectedOrganization( $clientId, @@ -825,15 +787,15 @@ public function authenticateWithSelectedOrganization( ?string $ipAddress = null, ?string $userAgent = null ) { - $path = "user_management/authenticate"; + $path = 'user_management/authenticate'; $params = [ - "client_id" => $clientId, - "pending_authentication_token" => $pendingAuthenticationToken, - "organization_id" => $organizationId, - "ip_address" => $ipAddress, - "user_agent" => $userAgent, - "grant_type" => "urn:workos:oauth:grant-type:organization-selection", - "client_secret" => WorkOS::getApiKey() + 'client_id' => $clientId, + 'pending_authentication_token' => $pendingAuthenticationToken, + 'organization_id' => $organizationId, + 'ip_address' => $ipAddress, + 'user_agent' => $userAgent, + 'grant_type' => 'urn:workos:oauth:grant-type:organization-selection', + 'client_secret' => WorkOS::getApiKey(), ]; $response = Client::request(Client::METHOD_POST, $path, null, $params, true); @@ -845,25 +807,24 @@ public function authenticateWithSelectedOrganization( * Authenticate an OAuth or SSO User with a Code * This should be used for "Hosted AuthKit" and "OAuth or SSO" UserAuthentications * - * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. - * @param string $code The authorization value which was passed back as a query parameter in the callback to the Redirect URI. - * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. - * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. + * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. + * @param string $code The authorization value which was passed back as a query parameter in the callback to the Redirect URI. + * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. + * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. + * @return Resource\AuthenticationResponse * * @throws Exception\WorkOSException - * - * @return Resource\AuthenticationResponse */ public function authenticateWithCode($clientId, $code, ?string $ipAddress = null, ?string $userAgent = null) { - $path = "user_management/authenticate"; + $path = 'user_management/authenticate'; $params = [ - "client_id" => $clientId, - "code" => $code, - "ip_address" => $ipAddress, - "user_agent" => $userAgent, - "grant_type" => "authorization_code", - "client_secret" => WorkOS::getApiKey() + 'client_id' => $clientId, + 'code' => $code, + 'ip_address' => $ipAddress, + 'user_agent' => $userAgent, + 'grant_type' => 'authorization_code', + 'client_secret' => WorkOS::getApiKey(), ]; $response = Client::request(Client::METHOD_POST, $path, null, $params, true); @@ -874,27 +835,26 @@ public function authenticateWithCode($clientId, $code, ?string $ipAddress = null /** * Authenticates a user with an unverified email and verifies their email address. * - * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. - * @param string $code The authorization value which was passed back as a query parameter in the callback to the Redirect URI. - * @param string $pendingAuthenticationToken Token returned from a failed authentication attempt due to organization selection being required. - * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. - * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. + * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. + * @param string $code The authorization value which was passed back as a query parameter in the callback to the Redirect URI. + * @param string $pendingAuthenticationToken Token returned from a failed authentication attempt due to organization selection being required. + * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. + * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. + * @return Resource\AuthenticationResponse * * @throws Exception\WorkOSException - * - * @return Resource\AuthenticationResponse */ public function authenticateWithEmailVerification($clientId, $code, $pendingAuthenticationToken, ?string $ipAddress = null, ?string $userAgent = null) { - $path = "user_management/authenticate"; + $path = 'user_management/authenticate'; $params = [ - "client_id" => $clientId, - "code" => $code, - "pending_authentication_token" => $pendingAuthenticationToken, - "ip_address" => $ipAddress, - "user_agent" => $userAgent, - "grant_type" => "urn:workos:oauth:grant-type:email-verification:code", - "client_secret" => WorkOS::getApiKey() + 'client_id' => $clientId, + 'code' => $code, + 'pending_authentication_token' => $pendingAuthenticationToken, + 'ip_address' => $ipAddress, + 'user_agent' => $userAgent, + 'grant_type' => 'urn:workos:oauth:grant-type:email-verification:code', + 'client_secret' => WorkOS::getApiKey(), ]; $response = Client::request(Client::METHOD_POST, $path, null, $params, true); @@ -905,17 +865,15 @@ public function authenticateWithEmailVerification($clientId, $code, $pendingAuth /** * Authenticate with Magic Auth * - * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. - * @param string $code The authorization value which was passed back as a query parameter in the callback to the Redirect URI. - * @param string $userId The unique ID of the user. - * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. - * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. + * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. + * @param string $code The authorization value which was passed back as a query parameter in the callback to the Redirect URI. + * @param string $userId The unique ID of the user. + * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. + * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. + * @return Resource\AuthenticationResponse * * @throws Exception\WorkOSException - * - * @return Resource\AuthenticationResponse */ - public function authenticateWithMagicAuth( $clientId, $code, @@ -923,15 +881,15 @@ public function authenticateWithMagicAuth( ?string $ipAddress = null, ?string $userAgent = null ) { - $path = "user_management/authenticate"; + $path = 'user_management/authenticate'; $params = [ - "client_id" => $clientId, - "code" => $code, - "user_id" => $userId, - "ip_address" => $ipAddress, - "user_agent" => $userAgent, - "grant_type" => "urn:workos:oauth:grant-type:magic-auth:code", - "client_secret" => WorkOS::getApiKey() + 'client_id' => $clientId, + 'code' => $code, + 'user_id' => $userId, + 'ip_address' => $ipAddress, + 'user_agent' => $userAgent, + 'grant_type' => 'urn:workos:oauth:grant-type:magic-auth:code', + 'client_secret' => WorkOS::getApiKey(), ]; $response = Client::request(Client::METHOD_POST, $path, null, $params, true); @@ -941,15 +899,15 @@ public function authenticateWithMagicAuth( /** * Authenticate with Refresh Token - * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. - * @param string $refreshToken The refresh token used to obtain a new access token - * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. - * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. - * @param string|null $organizationId The user agent of the request from the user who is attempting to authenticate. - * - * @throws Exception\WorkOSException * + * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. + * @param string $refreshToken The refresh token used to obtain a new access token + * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. + * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. + * @param string|null $organizationId The user agent of the request from the user who is attempting to authenticate. * @return Resource\AuthenticationResponse + * + * @throws Exception\WorkOSException */ public function authenticateWithRefreshToken( $clientId, @@ -958,15 +916,15 @@ public function authenticateWithRefreshToken( ?string $userAgent = null, ?string $organizationId = null ) { - $path = "user_management/authenticate"; + $path = 'user_management/authenticate'; $params = [ - "client_id" => $clientId, - "refresh_token" => $refreshToken, - "organization_id" => $organizationId, - "ip_address" => $ipAddress, - "user_agent" => $userAgent, - "grant_type" => "refresh_token", - "client_secret" => WorkOS::getApiKey() + 'client_id' => $clientId, + 'refresh_token' => $refreshToken, + 'organization_id' => $organizationId, + 'ip_address' => $ipAddress, + 'user_agent' => $userAgent, + 'grant_type' => 'refresh_token', + 'client_secret' => WorkOS::getApiKey(), ]; $response = Client::request(Client::METHOD_POST, $path, null, $params, true); @@ -977,16 +935,15 @@ public function authenticateWithRefreshToken( /** * Authenticate with TOTP * - * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. - * @param string $pendingAuthenticationToken - * @param string $authenticationChallengeId - * @param string $code - * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. - * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. + * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. + * @param string $pendingAuthenticationToken + * @param string $authenticationChallengeId + * @param string $code + * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. + * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. + * @return Resource\AuthenticationResponse * * @throws Exception\WorkOSException - * - * @return Resource\AuthenticationResponse */ public function authenticateWithTotp( $clientId, @@ -996,16 +953,16 @@ public function authenticateWithTotp( ?string $ipAddress = null, ?string $userAgent = null ) { - $path = "user_management/authenticate"; + $path = 'user_management/authenticate'; $params = [ - "client_id" => $clientId, - "pending_authentication_token" => $pendingAuthenticationToken, - "authentication_challenge_id" => $authenticationChallengeId, - "code" => $code, - "ip_address" => $ipAddress, - "user_agent" => $userAgent, - "grant_type" => "urn:workos:oauth:grant-type:mfa-totp", - "client_secret" => WorkOS::getApiKey() + 'client_id' => $clientId, + 'pending_authentication_token' => $pendingAuthenticationToken, + 'authentication_challenge_id' => $authenticationChallengeId, + 'code' => $code, + 'ip_address' => $ipAddress, + 'user_agent' => $userAgent, + 'grant_type' => 'urn:workos:oauth:grant-type:mfa-totp', + 'client_secret' => WorkOS::getApiKey(), ]; $response = Client::request(Client::METHOD_POST, $path, null, $params, true); @@ -1016,23 +973,22 @@ public function authenticateWithTotp( /** * Enroll An Authentication Factor. * - * @param string $userId The unique ID of the user. - * @param string $type The type of MFA factor used to authenticate. - * @param string|null $totpIssuer Your application or company name, this helps users distinguish between factors in authenticator apps. - * @param string|null $totpUser Used as the account name in authenticator apps. + * @param string $userId The unique ID of the user. + * @param string $type The type of MFA factor used to authenticate. + * @param string|null $totpIssuer Your application or company name, this helps users distinguish between factors in authenticator apps. + * @param string|null $totpUser Used as the account name in authenticator apps. + * @return Resource\AuthenticationFactorAndChallengeTotp * * @throws Exception\WorkOSException - * - * @return Resource\AuthenticationFactorAndChallengeTotp */ public function enrollAuthFactor($userId, $type, ?string $totpIssuer = null, ?string $totpUser = null) { $path = "user_management/users/{$userId}/auth_factors"; $params = [ - "type" => $type, - "totp_user" => $totpUser, - "totp_issuer" => $totpIssuer + 'type' => $type, + 'totp_user' => $totpUser, + 'totp_issuer' => $totpIssuer, ]; $response = Client::request(Client::METHOD_POST, $path, null, $params, true); @@ -1043,11 +999,10 @@ public function enrollAuthFactor($userId, $type, ?string $totpIssuer = null, ?st /** * List a User's Authentication Factors. * - * @param string $userId The unique ID of the user. + * @param string $userId The unique ID of the user. + * @return Resource\UserAuthenticationFactorTotp[] $authFactors A list of user's authentication factors * * @throws Exception\WorkOSException - * - * @return Resource\UserAuthenticationFactorTotp[] $authFactors A list of user's authentication factors */ public function listAuthFactors($userId) { @@ -1057,7 +1012,7 @@ public function listAuthFactors($userId) $authFactors = []; - foreach ($response["data"] as $responseData) { + foreach ($response['data'] as $responseData) { \array_push($authFactors, Resource\UserAuthenticationFactorTotp::constructFromResponse($responseData)); } @@ -1067,11 +1022,10 @@ public function listAuthFactors($userId) /** * Get an email verification object * - * @param string $emailVerificationId ID of the email verification object + * @param string $emailVerificationId ID of the email verification object + * @return Resource\EmailVerification * * @throws Exception\WorkOSException - * - * @return Resource\EmailVerification */ public function getEmailVerification($emailVerificationId) { @@ -1091,11 +1045,10 @@ public function getEmailVerification($emailVerificationId) /** * Create Email Verification Challenge. * - * @param string $userId The unique ID of the User whose email address will be verified. + * @param string $userId The unique ID of the User whose email address will be verified. + * @return Resource\UserResponse * * @throws Exception\WorkOSException - * - * @return Resource\UserResponse */ public function sendVerificationEmail($userId) { @@ -1109,19 +1062,18 @@ public function sendVerificationEmail($userId) /** * Complete Email Verification. * - * @param string $userId The unique ID of the user. - * @param string $code The one-time code emailed to the user. + * @param string $userId The unique ID of the user. + * @param string $code The one-time code emailed to the user. + * @return Resource\UserResponse * * @throws Exception\WorkOSException - * - * @return Resource\UserResponse */ public function verifyEmail($userId, $code) { $path = "user_management/users/{$userId}/email_verification/confirm"; $params = [ - "code" => $code + 'code' => $code, ]; $response = Client::request(Client::METHOD_POST, $path, null, $params, true); @@ -1132,11 +1084,10 @@ public function verifyEmail($userId, $code) /** * Get a password reset object * - * @param string $passwordResetId ID of the password reset object + * @param string $passwordResetId ID of the password reset object + * @return Resource\PasswordReset * * @throws Exception\WorkOSException - * - * @return Resource\PasswordReset */ public function getPasswordReset($passwordResetId) { @@ -1156,19 +1107,18 @@ public function getPasswordReset($passwordResetId) /** * Creates a password reset token * - * @param string $email The email address of the user + * @param string $email The email address of the user + * @return Resource\PasswordReset * * @throws Exception\WorkOSException - * - * @return Resource\PasswordReset */ public function createPasswordReset( $email ) { - $path = "user_management/password_reset"; + $path = 'user_management/password_reset'; $params = [ - "email" => $email + 'email' => $email, ]; $response = Client::request( @@ -1186,12 +1136,11 @@ public function createPasswordReset( * @deprecated 4.9.0 Use `createPasswordReset` instead. This method will be removed in a future major version. * Create Password Reset Email. * - * @param string $email The email of the user that wishes to reset their password. - * @param string $passwordResetUrl The URL that will be linked to in the email. + * @param string $email The email of the user that wishes to reset their password. + * @param string $passwordResetUrl The URL that will be linked to in the email. + * @return Resource\Response * * @throws Exception\WorkOSException - * - * @return Resource\Response */ public function sendPasswordResetEmail($email, $passwordResetUrl) { @@ -1199,11 +1148,11 @@ public function sendPasswordResetEmail($email, $passwordResetUrl) error_log($msg); - $path = "user_management/password_reset/send"; + $path = 'user_management/password_reset/send'; $params = [ - "email" => $email, - "password_reset_url" => $passwordResetUrl + 'email' => $email, + 'password_reset_url' => $passwordResetUrl, ]; $response = Client::request(Client::METHOD_POST, $path, null, $params, true); @@ -1214,20 +1163,19 @@ public function sendPasswordResetEmail($email, $passwordResetUrl) /** * Complete Password Reset. * - * @param string $token The reset token emailed to the user. - * @param string $newPassword The new password to be set for the user. + * @param string $token The reset token emailed to the user. + * @param string $newPassword The new password to be set for the user. + * @return Resource\UserResponse * * @throws Exception\WorkOSException - * - * @return Resource\UserResponse */ public function resetPassword($token, $newPassword) { - $path = "user_management/password_reset/confirm"; + $path = 'user_management/password_reset/confirm'; $params = [ - "token" => $token, - "new_password" => $newPassword + 'token' => $token, + 'new_password' => $newPassword, ]; $response = Client::request(Client::METHOD_POST, $path, null, $params, true); @@ -1238,11 +1186,10 @@ public function resetPassword($token, $newPassword) /** * Get a Magic Auth object * - * @param string $magicAuthId ID of the Magic Auth object + * @param string $magicAuthId ID of the Magic Auth object + * @return Resource\MagicAuth * * @throws Exception\WorkOSException - * - * @return Resource\MagicAuth */ public function getMagicAuth($magicAuthId) { @@ -1262,22 +1209,21 @@ public function getMagicAuth($magicAuthId) /** * Creates a Magic Auth code * - * @param string $email The email address of the user - * @param string|null $invitationToken The token of an Invitation, if required. + * @param string $email The email address of the user + * @param string|null $invitationToken The token of an Invitation, if required. + * @return Resource\MagicAuth * * @throws Exception\WorkOSException - * - * @return Resource\MagicAuth */ public function createMagicAuth( $email, ?string $invitationToken = null ) { - $path = "user_management/magic_auth"; + $path = 'user_management/magic_auth'; $params = [ - "email" => $email, - "invitation_token" => $invitationToken + 'email' => $email, + 'invitation_token' => $invitationToken, ]; $response = Client::request( @@ -1295,18 +1241,17 @@ public function createMagicAuth( * @deprecated 4.6.0 Use `createMagicAuth` instead. This method will be removed in a future major version. * Creates a one-time Magic Auth code and emails it to the user. * - * @param string $email The email address the one-time code will be sent to. + * @param string $email The email address the one-time code will be sent to. + * @return Resource\Response * * @throws Exception\WorkOSException - * - * @return Resource\Response */ public function sendMagicAuthCode($email) { - $path = "user_management/magic_auth/send"; + $path = 'user_management/magic_auth/send'; $params = [ - "email" => $email, + 'email' => $email, ]; $msg = "'sendMagicAuthCode' is deprecated. Please use 'createMagicAuth' instead. This method will be removed in a future major version."; @@ -1327,16 +1272,15 @@ public function sendMagicAuthCode($email) /** * Returns the public key host that is used for verifying access tokens. * - * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. + * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. + * @return string * * @throws Exception\UnexpectedValueException - * - * @return string */ public function getJwksUrl(string $clientId) { - if (!isset($clientId) || empty($clientId)) { - throw new Exception\UnexpectedValueException("clientId must not be empty"); + if (! isset($clientId) || empty($clientId)) { + throw new Exception\UnexpectedValueException('clientId must not be empty'); } $baseUrl = WorkOS::getApiBaseUrl(); @@ -1347,23 +1291,22 @@ public function getJwksUrl(string $clientId) /** * Returns the logout URL to end a user's session and redirect to your home page. * - * @param string $sessionId The session ID of the user. - * @param string|null $return_to The URL to redirect to after the user logs out. - * + * @param string $sessionId The session ID of the user. + * @param string|null $return_to The URL to redirect to after the user logs out. * @return string */ public function getLogoutUrl(string $sessionId, ?string $return_to = null) { - if (!isset($sessionId) || empty($sessionId)) { - throw new Exception\UnexpectedValueException("sessionId must not be empty"); + if (! isset($sessionId) || empty($sessionId)) { + throw new Exception\UnexpectedValueException('sessionId must not be empty'); } - $params = [ "session_id" => $sessionId ]; + $params = ['session_id' => $sessionId]; if ($return_to) { - $params["return_to"] = $return_to; + $params['return_to'] = $return_to; } - return Client::generateUrl("user_management/sessions/logout", $params); + return Client::generateUrl('user_management/sessions/logout', $params); } /** @@ -1376,8 +1319,8 @@ public function getLogoutUrl(string $sessionId, ?string $return_to = null) * - 'after' (string|null): Session ID to look after * - 'order' (string|null): The order in which to paginate records * - * @return array{?string, ?string, Resource\Session[]} - * An array containing before/after cursors and array of Session instances + * @return Resource\PaginatedResource A paginated resource containing before/after cursors and sessions array. + * Supports: [$before, $after, $sessions] = $result, ["sessions" => $sessions] = $result, $result->sessions * @throws Exception\WorkOSException */ public function listSessions(string $userId, array $options = []) @@ -1399,14 +1342,7 @@ public function listSessions(string $userId, array $options = []) true ); - $sessions = []; - list($before, $after) = Util\Request::parsePaginationArgs($response); - - foreach ($response["data"] as $responseData) { - \array_push($sessions, Resource\Session::constructFromResponse($responseData)); - } - - return [$before, $after, $sessions]; + return Resource\PaginatedResource::constructFromResponse($response, Resource\Session::class, 'sessions'); } /** diff --git a/lib/Vault.php b/lib/Vault.php index 2f4fd50..d3e7bfe 100644 --- a/lib/Vault.php +++ b/lib/Vault.php @@ -37,7 +37,8 @@ public function getVaultObject($vaultObjectId) * @param null|string $after Vault Object ID to look after * @param Resource\Order $order The Order in which to paginate records * - * @return array{?string, ?string, Resource\VaultObject[]} An array containing the Vault Object ID to use as before and after cursor, and an array of VaultObject instances + * @return Resource\PaginatedResource A paginated resource containing before/after cursors and vault_objects array. + * Supports: [$before, $after, $objects] = $result, ["vault_objects" => $objects] = $result, $result->vault_objects * * @throws Exception\WorkOSException */ @@ -63,13 +64,7 @@ public function listVaultObjects( true ); - $vaultObjects = []; - list($before, $after) = Util\Request::parsePaginationArgs($response); - foreach ($response["data"] as $responseData) { - \array_push($vaultObjects, Resource\VaultObject::constructFromResponse($responseData)); - } - - return [$before, $after, $vaultObjects]; + return Resource\PaginatedResource::constructFromResponse($response, Resource\VaultObject::class, 'vault_objects'); } diff --git a/tests/WorkOS/DirectorySyncTest.php b/tests/WorkOS/DirectorySyncTest.php index 7dfe236..09c140f 100644 --- a/tests/WorkOS/DirectorySyncTest.php +++ b/tests/WorkOS/DirectorySyncTest.php @@ -54,6 +54,76 @@ public function testListDirectories() $this->assertSame($directory, $directories[0]->toArray()); } + public function testListDirectoriesPaginatedResourceAccessPatterns() + { + $directoriesPath = "directories"; + $params = [ + "limit" => DirectorySync::DEFAULT_PAGE_SIZE, + "before" => null, + "after" => null, + "domain" => null, + "search" => null, + "organization_id" => null, + "order" => null + ]; + + $result = $this->directoriesResponseFixture(); + + $this->mockRequest( + Client::METHOD_GET, + $directoriesPath, + null, + $params, + true, + $result + ); + + // Test 1: Bare destructuring (indexed) + [$before1, $after1, $directories1] = $this->ds->listDirectories(); + $this->assertNull($before1); + $this->assertNull($after1); + $this->assertIsArray($directories1); + $this->assertCount(1, $directories1); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $directoriesPath, + null, + $params, + true, + $result + ); + + // Test 2: Named destructuring + ["before" => $before2, "after" => $after2, "directories" => $directories2] = $this->ds->listDirectories(); + $this->assertNull($before2); + $this->assertNull($after2); + $this->assertIsArray($directories2); + $this->assertCount(1, $directories2); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $directoriesPath, + null, + $params, + true, + $result + ); + + // Test 3: Fluent access + $response = $this->ds->listDirectories(); + $this->assertNull($response->before); + $this->assertNull($response->after); + $this->assertIsArray($response->directories); + $this->assertCount(1, $response->directories); + + // Test 4: Generic data accessor + $this->assertIsArray($response->data); + $this->assertSame($response->directories, $response->data); + } + public function testGetDirectory() { $directoryId = "directory_id"; @@ -125,6 +195,73 @@ public function testListGroups() $this->assertSame($group, $groups[0]->toArray()); } + public function testListGroupsPaginatedResourceAccessPatterns() + { + $usersPath = "directory_groups"; + $params = [ + "limit" => DirectorySync::DEFAULT_PAGE_SIZE, + "before" => null, + "after" => null, + "order" => null + ]; + + $result = $this->groupsResponseFixture(); + + $this->mockRequest( + Client::METHOD_GET, + $usersPath, + null, + $params, + true, + $result + ); + + // Test 1: Bare destructuring (indexed) + [$before1, $after1, $groups1] = $this->ds->listGroups(); + $this->assertNull($before1); + $this->assertNull($after1); + $this->assertIsArray($groups1); + $this->assertCount(1, $groups1); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $usersPath, + null, + $params, + true, + $result + ); + + // Test 2: Named destructuring + ["before" => $before2, "after" => $after2, "groups" => $groups2] = $this->ds->listGroups(); + $this->assertNull($before2); + $this->assertNull($after2); + $this->assertIsArray($groups2); + $this->assertCount(1, $groups2); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $usersPath, + null, + $params, + true, + $result + ); + + // Test 3: Fluent access + $response = $this->ds->listGroups(); + $this->assertNull($response->before); + $this->assertNull($response->after); + $this->assertIsArray($response->groups); + $this->assertCount(1, $response->groups); + + // Test 4: Generic data accessor + $this->assertIsArray($response->data); + $this->assertSame($response->groups, $response->data); + } + public function testGetUser() { $directoryUser = "directory_usr_id"; @@ -218,6 +355,73 @@ public function testListUsers() $this->assertEquals($user, $users[0]->toArray()); } + public function testListUsersPaginatedResourceAccessPatterns() + { + $usersPath = "directory_users"; + $params = [ + "limit" => DirectorySync::DEFAULT_PAGE_SIZE, + "before" => null, + "after" => null, + "order" => null + ]; + + $result = $this->usersResponseFixture(); + + $this->mockRequest( + Client::METHOD_GET, + $usersPath, + null, + $params, + true, + $result + ); + + // Test 1: Bare destructuring (indexed) + [$before1, $after1, $users1] = $this->ds->listUsers(); + $this->assertNull($before1); + $this->assertNull($after1); + $this->assertIsArray($users1); + $this->assertCount(1, $users1); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $usersPath, + null, + $params, + true, + $result + ); + + // Test 2: Named destructuring + ["before" => $before2, "after" => $after2, "users" => $users2] = $this->ds->listUsers(); + $this->assertNull($before2); + $this->assertNull($after2); + $this->assertIsArray($users2); + $this->assertCount(1, $users2); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $usersPath, + null, + $params, + true, + $result + ); + + // Test 3: Fluent access + $response = $this->ds->listUsers(); + $this->assertNull($response->before); + $this->assertNull($response->after); + $this->assertIsArray($response->users); + $this->assertCount(1, $response->users); + + // Test 4: Generic data accessor + $this->assertIsArray($response->data); + $this->assertSame($response->users, $response->data); + } + public function testDeleteDirectory() { $directory = "directory_id"; diff --git a/tests/WorkOS/OrganizationsTest.php b/tests/WorkOS/OrganizationsTest.php index 6826798..eea6911 100644 --- a/tests/WorkOS/OrganizationsTest.php +++ b/tests/WorkOS/OrganizationsTest.php @@ -174,6 +174,74 @@ public function testListOrganizations() $this->assertSame($organization, $organizations[0]->toArray()); } + public function testListOrganizationsPaginatedResourceAccessPatterns() + { + $organizationsPath = "organizations"; + $params = [ + "limit" => Organizations::DEFAULT_PAGE_SIZE, + "before" => null, + "after" => null, + "domains" => null, + "order" => null + ]; + + $result = $this->organizationsResponseFixture(); + + $this->mockRequest( + Client::METHOD_GET, + $organizationsPath, + null, + $params, + true, + $result + ); + + // Test 1: Bare destructuring (indexed) + [$before1, $after1, $organizations1] = $this->organizations->listOrganizations(); + $this->assertSame("before-id", $before1); + $this->assertNull($after1); + $this->assertIsArray($organizations1); + $this->assertCount(3, $organizations1); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $organizationsPath, + null, + $params, + true, + $result + ); + + // Test 2: Named destructuring + ["before" => $before2, "after" => $after2, "organizations" => $organizations2] = $this->organizations->listOrganizations(); + $this->assertSame("before-id", $before2); + $this->assertNull($after2); + $this->assertIsArray($organizations2); + $this->assertCount(3, $organizations2); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $organizationsPath, + null, + $params, + true, + $result + ); + + // Test 3: Fluent access + $response = $this->organizations->listOrganizations(); + $this->assertSame("before-id", $response->before); + $this->assertNull($response->after); + $this->assertIsArray($response->organizations); + $this->assertCount(3, $response->organizations); + + // Test 4: Generic data accessor + $this->assertIsArray($response->data); + $this->assertSame($response->organizations, $response->data); + } + public function testListOrganizationRoles() { $organizationRolesPath = "organizations/org_01EHQMYV6MBK39QC5PZXHY59C3/roles"; @@ -223,6 +291,72 @@ public function testListOrganizationFeatureFlags() $this->assertSame($featureFlag, $featureFlags[0]->toArray()); } + public function testListOrganizationFeatureFlagsPaginatedResourceAccessPatterns() + { + $featureFlagsPath = "organizations/org_01EHQMYV6MBK39QC5PZXHY59C3/feature-flags"; + $result = $this->featureFlagsResponseFixture(); + $params = [ + "limit" => 10, + "before" => null, + "after" => null, + "order" => null + ]; + + $this->mockRequest( + Client::METHOD_GET, + $featureFlagsPath, + null, + $params, + true, + $result + ); + + // Test 1: Bare destructuring (indexed) + [$before1, $after1, $flags1] = $this->organizations->listOrganizationFeatureFlags("org_01EHQMYV6MBK39QC5PZXHY59C3"); + $this->assertSame("", $before1); + $this->assertSame("", $after1); + $this->assertIsArray($flags1); + $this->assertCount(3, $flags1); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $featureFlagsPath, + null, + $params, + true, + $result + ); + + // Test 2: Named destructuring + ["before" => $before2, "after" => $after2, "feature_flags" => $flags2] = $this->organizations->listOrganizationFeatureFlags("org_01EHQMYV6MBK39QC5PZXHY59C3"); + $this->assertSame("", $before2); + $this->assertSame("", $after2); + $this->assertIsArray($flags2); + $this->assertCount(3, $flags2); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $featureFlagsPath, + null, + $params, + true, + $result + ); + + // Test 3: Fluent access + $response = $this->organizations->listOrganizationFeatureFlags("org_01EHQMYV6MBK39QC5PZXHY59C3"); + $this->assertSame("", $response->before); + $this->assertSame("", $response->after); + $this->assertIsArray($response->feature_flags); + $this->assertCount(3, $response->feature_flags); + + // Test 4: Generic data accessor + $this->assertIsArray($response->data); + $this->assertSame($response->feature_flags, $response->data); + } + // Fixtures private function createOrganizationResponseFixture() diff --git a/tests/WorkOS/PaginatedResourceTest.php b/tests/WorkOS/PaginatedResourceTest.php new file mode 100644 index 0000000..92b48e9 --- /dev/null +++ b/tests/WorkOS/PaginatedResourceTest.php @@ -0,0 +1,229 @@ +makePaginatedResource(null, 'cursor_abc', ['a', 'b'], 'users'); + $this->assertInstanceOf(PaginatedResource::class, $resource); + } + + public function testConstructRejectsReservedDataKeyBefore() + { + $this->expectException(\InvalidArgumentException::class); + new PaginatedResource(null, null, [], 'before'); + } + + public function testConstructRejectsReservedDataKeyAfter() + { + $this->expectException(\InvalidArgumentException::class); + new PaginatedResource(null, null, [], 'after'); + } + + public function testConstructRejectsReservedDataKeyData() + { + $this->expectException(\InvalidArgumentException::class); + new PaginatedResource(null, null, [], 'data'); + } + + // -- Bare destructuring (numeric offsets) -- + + public function testBareDestructuring() + { + $resource = $this->makePaginatedResource('cur_before', 'cur_after', ['x', 'y']); + + [$before, $after, $items] = $resource; + + $this->assertSame('cur_before', $before); + $this->assertSame('cur_after', $after); + $this->assertSame(['x', 'y'], $items); + } + + public function testBareDestructuringWithNullCursors() + { + $resource = $this->makePaginatedResource(null, null, ['x']); + + [$before, $after, $items] = $resource; + + $this->assertNull($before); + $this->assertNull($after); + $this->assertSame(['x'], $items); + } + + // -- Named destructuring (string offsets) -- + + public function testNamedDestructuring() + { + $resource = $this->makePaginatedResource('b', 'a', [1, 2], 'users'); + + ["before" => $before, "after" => $after, "users" => $users] = $resource; + + $this->assertSame('b', $before); + $this->assertSame('a', $after); + $this->assertSame([1, 2], $users); + } + + public function testGenericDataKeyAccess() + { + $resource = $this->makePaginatedResource(null, null, [1, 2], 'users'); + + $this->assertSame($resource['data'], $resource['users']); + } + + // -- Fluent property access (__get) -- + + public function testFluentPropertyAccess() + { + $resource = $this->makePaginatedResource('b', 'a', [1], 'directories'); + + $this->assertSame('b', $resource->before); + $this->assertSame('a', $resource->after); + $this->assertSame([1], $resource->directories); + $this->assertSame([1], $resource->data); + } + + public function testFluentAccessUnknownPropertyReturnsNull() + { + $resource = $this->makePaginatedResource(null, null, []); + + $this->assertNull($resource->nonexistent); + $this->assertNull($resource->typo_property); + } + + // -- offsetExists -- + + public function testOffsetExistsNumericIndices() + { + $resource = $this->makePaginatedResource(); + + $this->assertTrue(isset($resource[0])); + $this->assertTrue(isset($resource[1])); + $this->assertTrue(isset($resource[2])); + $this->assertFalse(isset($resource[3])); + $this->assertFalse(isset($resource[-1])); + } + + public function testOffsetExistsStringKeys() + { + $resource = $this->makePaginatedResource(null, null, [], 'users'); + + $this->assertTrue(isset($resource['before'])); + $this->assertTrue(isset($resource['after'])); + $this->assertTrue(isset($resource['data'])); + $this->assertTrue(isset($resource['users'])); + $this->assertFalse(isset($resource['nonexistent'])); + } + + // -- __isset -- + + public function testIssetReturnsFalseForNullCursors() + { + $resource = $this->makePaginatedResource(null, null, []); + + $this->assertFalse(isset($resource->before)); + $this->assertFalse(isset($resource->after)); + } + + public function testIssetReturnsTrueForNonNullCursors() + { + $resource = $this->makePaginatedResource('b', 'a', []); + + $this->assertTrue(isset($resource->before)); + $this->assertTrue(isset($resource->after)); + } + + public function testIssetReturnsTrueForDataKey() + { + $resource = $this->makePaginatedResource(null, null, [], 'users'); + + $this->assertTrue(isset($resource->data)); + $this->assertTrue(isset($resource->users)); + } + + public function testIssetReturnsFalseForUnknownProperties() + { + $resource = $this->makePaginatedResource(); + + $this->assertFalse(isset($resource->nonexistent)); + } + + // -- Immutability -- + + public function testOffsetSetThrows() + { + $resource = $this->makePaginatedResource(); + + $this->expectException(\BadMethodCallException::class); + $resource[0] = 'value'; + } + + public function testOffsetUnsetThrows() + { + $resource = $this->makePaginatedResource(); + + $this->expectException(\BadMethodCallException::class); + unset($resource[0]); + } + + // -- Countable -- + + public function testCountReturnsDataItemCount() + { + $this->assertCount(0, $this->makePaginatedResource(null, null, [])); + $this->assertCount(3, $this->makePaginatedResource(null, null, ['a', 'b', 'c'])); + $this->assertCount(1, $this->makePaginatedResource(null, null, ['x'])); + } + + // -- IteratorAggregate -- + + public function testForeachIteratesDataItems() + { + $data = ['item1', 'item2', 'item3']; + $resource = $this->makePaginatedResource(null, null, $data); + + $collected = []; + foreach ($resource as $item) { + $collected[] = $item; + } + + $this->assertSame($data, $collected); + } + + public function testForeachOnEmptyData() + { + $resource = $this->makePaginatedResource(); + + $collected = []; + foreach ($resource as $item) { + $collected[] = $item; + } + + $this->assertSame([], $collected); + } + + // -- offsetGet edge cases -- + + public function testOffsetGetReturnsNullForUnknownOffset() + { + $resource = $this->makePaginatedResource(); + + $this->assertNull($resource[99]); + $this->assertNull($resource['unknown_key']); + } +} diff --git a/tests/WorkOS/SSOTest.php b/tests/WorkOS/SSOTest.php index 1d16824..7c25663 100644 --- a/tests/WorkOS/SSOTest.php +++ b/tests/WorkOS/SSOTest.php @@ -172,6 +172,76 @@ public function testListConnections() $this->assertSame($connection, $connections[0]->toArray()); } + public function testListConnectionsPaginatedResourceAccessPatterns() + { + $connectionsPath = "connections"; + $params = [ + "limit" => SSO::DEFAULT_PAGE_SIZE, + "before" => null, + "after" => null, + "domain" => null, + "connection_type" => null, + "organization_id" => null, + "order" => null + ]; + + $result = $this->connectionsResponseFixture(); + + $this->mockRequest( + Client::METHOD_GET, + $connectionsPath, + null, + $params, + true, + $result + ); + + // Test 1: Bare destructuring (indexed) + [$before1, $after1, $connections1] = $this->sso->listConnections(); + $this->assertNull($before1); + $this->assertNull($after1); + $this->assertIsArray($connections1); + $this->assertCount(1, $connections1); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $connectionsPath, + null, + $params, + true, + $result + ); + + // Test 2: Named destructuring + ["before" => $before2, "after" => $after2, "connections" => $connections2] = $this->sso->listConnections(); + $this->assertNull($before2); + $this->assertNull($after2); + $this->assertIsArray($connections2); + $this->assertCount(1, $connections2); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $connectionsPath, + null, + $params, + true, + $result + ); + + // Test 3: Fluent access + $response = $this->sso->listConnections(); + $this->assertNull($response->before); + $this->assertNull($response->after); + $this->assertIsArray($response->connections); + $this->assertCount(1, $response->connections); + + // Test 4: Generic data accessor + $this->assertIsArray($response->data); + $this->assertSame($response->connections, $response->data); + } + public function testDeleteConnection() { $connection = "connection_id"; diff --git a/tests/WorkOS/UserManagementTest.php b/tests/WorkOS/UserManagementTest.php index 269b897..8a7e330 100644 --- a/tests/WorkOS/UserManagementTest.php +++ b/tests/WorkOS/UserManagementTest.php @@ -881,6 +881,75 @@ public function testListUsers() $this->assertSame($user, $users[0]->toArray()); } + public function testListUsersPaginatedResourceAccessPatterns() + { + $path = "user_management/users"; + $params = [ + "email" => null, + "organization_id" => null, + "limit" => UserManagement::DEFAULT_PAGE_SIZE, + "before" => null, + "after" => null, + "order" => null + ]; + + $result = $this->listUsersResponseFixture(); + + $this->mockRequest( + Client::METHOD_GET, + $path, + null, + $params, + true, + $result + ); + + // Test 1: Bare destructuring (indexed) + [$before1, $after1, $users1] = $this->userManagement->listUsers(); + $this->assertNull($before1); + $this->assertNull($after1); + $this->assertIsArray($users1); + $this->assertCount(1, $users1); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $path, + null, + $params, + true, + $result + ); + + // Test 2: Named destructuring + ["before" => $before2, "after" => $after2, "users" => $users2] = $this->userManagement->listUsers(); + $this->assertNull($before2); + $this->assertNull($after2); + $this->assertIsArray($users2); + $this->assertCount(1, $users2); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $path, + null, + $params, + true, + $result + ); + + // Test 3: Fluent access + $response = $this->userManagement->listUsers(); + $this->assertNull($response->before); + $this->assertNull($response->after); + $this->assertIsArray($response->users); + $this->assertCount(1, $response->users); + + // Test 4: Generic data accessor + $this->assertIsArray($response->data); + $this->assertSame($response->users, $response->data); + } + public function testGetMagicAuth() { $magicAuthId = "magic_auth_01E4ZCR3C56J083X43JQXF3JK5"; diff --git a/tests/WorkOS/VaultTest.php b/tests/WorkOS/VaultTest.php index 2b3e00d..2160b35 100644 --- a/tests/WorkOS/VaultTest.php +++ b/tests/WorkOS/VaultTest.php @@ -72,6 +72,72 @@ public function testListVaultObjects() $this->assertSame($vaultObjects, $response[0]->toArray()); } + public function testListVaultObjectsPaginatedResourceAccessPatterns() + { + $vaultObjectsPath = "vault/v1/kv"; + $result = $this->vaultObjectsResponseFixture(); + $params = [ + "limit" => 10, + "before" => null, + "after" => null, + "order" => null + ]; + + $this->mockRequest( + Client::METHOD_GET, + $vaultObjectsPath, + null, + $params, + true, + $result + ); + + // Test 1: Bare destructuring (indexed) + [$before1, $after1, $objects1] = $this->vault->listVaultObjects(); + $this->assertNull($before1); + $this->assertNull($after1); + $this->assertIsArray($objects1); + $this->assertCount(1, $objects1); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $vaultObjectsPath, + null, + $params, + true, + $result + ); + + // Test 2: Named destructuring + ["before" => $before2, "after" => $after2, "vault_objects" => $objects2] = $this->vault->listVaultObjects(); + $this->assertNull($before2); + $this->assertNull($after2); + $this->assertIsArray($objects2); + $this->assertCount(1, $objects2); + + // Mock the request again for the next test + $this->mockRequest( + Client::METHOD_GET, + $vaultObjectsPath, + null, + $params, + true, + $result + ); + + // Test 3: Fluent access + $response = $this->vault->listVaultObjects(); + $this->assertNull($response->before); + $this->assertNull($response->after); + $this->assertIsArray($response->vault_objects); + $this->assertCount(1, $response->vault_objects); + + // Test 4: Generic data accessor + $this->assertIsArray($response->data); + $this->assertSame($response->vault_objects, $response->data); + } +