diff --git a/composer.json b/composer.json index 6fc4829..330abf6 100644 --- a/composer.json +++ b/composer.json @@ -19,11 +19,14 @@ ], "require": { "php": ">=8.1.0", + "illuminate/contracts": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", "workos/workos-php": "^v4.29.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.15 || ^3.6", - "phpunit/phpunit": "^5.7 || ^10.1" + "friendsofphp/php-cs-fixer": "^3.64", + "phpunit/phpunit": "^10.1 || ^11.0", + "orchestra/testbench": "^8.0|^9.0|^10.0" }, "suggest": { "laravel/framework": "For testing" @@ -31,7 +34,10 @@ "autoload": { "psr-4": { "WorkOS\\Laravel\\": "lib/" - } + }, + "files": [ + "lib/helpers.php" + ] }, "autoload-dev": { "psr-4": { @@ -45,7 +51,10 @@ "laravel": { "providers": [ "WorkOS\\Laravel\\WorkOSServiceProvider" - ] + ], + "aliases": { + "WorkOS": "WorkOS\\Laravel\\Facades\\WorkOS" + } } }, "scripts": { diff --git a/lib/Facades/WorkOS.php b/lib/Facades/WorkOS.php new file mode 100644 index 0000000..75fc84b --- /dev/null +++ b/lib/Facades/WorkOS.php @@ -0,0 +1,26 @@ + AuditLogs::class, + 'directorySync' => DirectorySync::class, + 'mfa' => MFA::class, + 'organizations' => Organizations::class, + 'portal' => Portal::class, + 'sso' => SSO::class, + 'userManagement' => UserManagement::class, + ]; + + /** + * Dynamically resolve a WorkOS service. + * + * @param string $name + * @param array $arguments + * @return mixed + */ + public function __call($name, $arguments) + { + if (! array_key_exists($name, $this->serviceMap)) { + throw new InvalidArgumentException("WorkOS service [$name] is not supported."); + } + + if (isset($this->instances[$name])) { + return $this->instances[$name]; + } + + return $this->instances[$name] = $arguments ? new $this->serviceMap[$name]($arguments) : new $this->serviceMap[$name]; + } +} diff --git a/lib/WorkOSServiceProvider.php b/lib/WorkOSServiceProvider.php index a9faa49..eacc908 100644 --- a/lib/WorkOSServiceProvider.php +++ b/lib/WorkOSServiceProvider.php @@ -1,8 +1,11 @@ app->runningInConsole()) { $this->publishes( - [__DIR__."/../config/workos.php" => config_path("workos.php")] + [__DIR__.'/../config/workos.php' => config_path('workos.php')], + 'workos-config' ); } } @@ -24,18 +28,29 @@ public function boot() /** * Register the ServiceProvider as well as setup WorkOS. */ - public function register() + public function register(): void { - $this->mergeConfigFrom(__DIR__."/../config/workos.php", "workos"); + $this->mergeConfigFrom(__DIR__.'/../config/workos.php', 'workos'); - $config = $this->app["config"]->get("workos"); - \WorkOS\WorkOS::setApiKey($config["api_key"]); - \WorkOS\WorkOS::setClientId($config["client_id"]); - \WorkOS\WorkOS::setIdentifier(\WorkOS\Laravel\Version::SDK_IDENTIFIER); - \WorkOS\WorkOS::setVersion(\WorkOS\Laravel\Version::SDK_VERSION); + // Ensures that the WorkOS service is configured only once, rather than every request + $this->app->singleton('workos', function ($app) { + $config = $app['config']->get('workos'); - if ($config["api_base_url"]) { - \WorkOS\WorkOS::setApiBaseUrl($config["api_base_url"]); - } + \WorkOS\WorkOS::setApiKey($config['api_key']); + \WorkOS\WorkOS::setClientId($config['client_id']); + \WorkOS\WorkOS::setIdentifier(Version::SDK_IDENTIFIER); + \WorkOS\WorkOS::setVersion(Version::SDK_VERSION); + + if ($config['api_base_url']) { + \WorkOS\WorkOS::setApiBaseUrl($config['api_base_url']); + } + + return new WorkOSService; + }); + + // Allows for dependency injection (e.g. `show(WorkOSService $service)`) + // while still ensuring we're using the configured singleton rather than + // potentially generating a new, unconfigured version of the singleton + $this->app->alias('workos', WorkOSService::class); } } diff --git a/lib/helpers.php b/lib/helpers.php new file mode 100644 index 0000000..770399c --- /dev/null +++ b/lib/helpers.php @@ -0,0 +1,17 @@ +app = $this->setupApplication(); + $this->setDefaultConfig(); + $this->setupProvider($this->app); + } + + protected function setDefaultConfig(array $overrides = []): void + { + $defaults = [ + 'api_key' => 'pk_test', + 'client_id' => 'client_test', + ]; + + foreach (array_merge($defaults, $overrides) as $key => $value) { + $this->app['config']->set("workos.{$key}", $value); + } + } + + public function test_facade_resolves_workos_service() + { + WorkOS::setFacadeApplication($this->app); + + $this->assertInstanceOf(\WorkOS\UserManagement::class, WorkOS::userManagement()); + } + + public function test_facade_provides_access_to_all_services() + { + WorkOS::setFacadeApplication($this->app); + + $this->assertInstanceOf(\WorkOS\AuditLogs::class, WorkOS::auditLogs()); + $this->assertInstanceOf(\WorkOS\DirectorySync::class, WorkOS::directorySync()); + $this->assertInstanceOf(\WorkOS\MFA::class, WorkOS::mfa()); + $this->assertInstanceOf(\WorkOS\Organizations::class, WorkOS::organizations()); + $this->assertInstanceOf(\WorkOS\Portal::class, WorkOS::portal()); + $this->assertInstanceOf(\WorkOS\SSO::class, WorkOS::sso()); + $this->assertInstanceOf(\WorkOS\UserManagement::class, WorkOS::userManagement()); + } +} diff --git a/tests/WorkOS/WorkOSServiceProviderTest.php b/tests/WorkOS/WorkOSServiceProviderTest.php index 5d91826..661a035 100644 --- a/tests/WorkOS/WorkOSServiceProviderTest.php +++ b/tests/WorkOS/WorkOSServiceProviderTest.php @@ -2,6 +2,8 @@ namespace WorkOS\Laravel; +use WorkOS\Laravel\Services\WorkOSService; + class WorkOSServiceProviderTest extends LaravelTestCase { protected $app; @@ -9,17 +11,95 @@ class WorkOSServiceProviderTest extends LaravelTestCase protected function setUp(): void { $this->app = $this->setupApplication(); + $this->setDefaultConfig(); + $this->setupProvider($this->app); } - public function testRegisterWorkOSServiceProviderYieldsExpectedConfig() + protected function setDefaultConfig(array $overrides = []): void { - $this->app["config"]->set("workos.api_key", "pk_secretsauce"); - $this->app["config"]->set("workos.client_id", "client_pizza"); - $this->app["config"]->set("workos.api_base_url", "https://workos-hop.com/"); - $this->setupProvider($this->app); + $defaults = [ + 'api_key' => 'pk_test', + 'client_id' => 'client_test', + ]; + + foreach (array_merge($defaults, $overrides) as $key => $value) { + $this->app['config']->set("workos.{$key}", $value); + } + } + + public function test_register_work_os_service_provider_yields_expected_config() + { + $this->setDefaultConfig([ + 'api_key' => 'pk_secretsauce', + 'client_id' => 'client_pizza', + 'api_base_url' => 'https://workos-hop.com/', + ]); + + // Resolve the service to trigger lazy initialization + $this->app->make('workos'); + + $this->assertEquals('pk_secretsauce', \WorkOS\WorkOS::getApiKey()); + $this->assertEquals('client_pizza', \WorkOS\WorkOS::getClientId()); + $this->assertEquals('https://workos-hop.com/', \WorkOS\WorkOS::getApiBaseUrl()); + } + + public function test_workos_helper_function_returns_work_os_service_instance() + { + $this->assertInstanceOf(WorkOSService::class, workos()); + } + + public function test_workos_helper_function_enables_fluent_access() + { + $this->assertInstanceOf(\WorkOS\UserManagement::class, workos()->userManagement()); + } + + public function test_it_resolves_service_via_injection_and_configures_sdk() + { + $service = $this->app->make(WorkOSService::class); + + $this->assertInstanceOf(WorkOSService::class, $service); + $this->assertSame($service, $this->app->make('workos')); + $this->assertSame($service, workos()); + } + + public function test_workos_service_resolves_all_supported_services() + { + $service = workos(); + + $this->assertInstanceOf(\WorkOS\AuditLogs::class, $service->auditLogs()); + $this->assertInstanceOf(\WorkOS\DirectorySync::class, $service->directorySync()); + $this->assertInstanceOf(\WorkOS\MFA::class, $service->mfa()); + $this->assertInstanceOf(\WorkOS\Organizations::class, $service->organizations()); + $this->assertInstanceOf(\WorkOS\Portal::class, $service->portal()); + $this->assertInstanceOf(\WorkOS\SSO::class, $service->sso()); + $this->assertInstanceOf(\WorkOS\UserManagement::class, $service->userManagement()); + } + + public function test_workos_service_caches_service_instances() + { + $service = workos(); + + $userManagement1 = $service->userManagement(); + $userManagement2 = $service->userManagement(); + + $this->assertSame($userManagement1, $userManagement2); + } + + public function test_workos_service_throws_exception_for_unsupported_service() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('WorkOS service [unsupportedService] is not supported.'); + + $service = workos(); + $service->unsupportedService(); + } + + public function test_api_base_url_is_set_when_provided() + { + $this->setDefaultConfig(['api_base_url' => 'https://custom-api.workos.com/']); + + $this->app->make('workos'); - $this->assertEquals("pk_secretsauce", \WorkOS\WorkOS::getApiKey()); - $this->assertEquals("client_pizza", \WorkOS\WorkOS::getClientId()); - $this->assertEquals("https://workos-hop.com/", \WorkOS\WorkOS::getApiBaseUrl()); + $this->assertEquals('https://custom-api.workos.com/', \WorkOS\WorkOS::getApiBaseUrl()); } }