[ ...$i, new WooCommerceIntegration() ] ); * * That is also the seam a genuinely third-party plugin uses, which a glob over this plugin's own * directories could never be. * * THE REGISTRY IS NOT A CATALOGUE. It holds connectors whose CODE IS PRESENT, which is why * {@see \PlugixaCrm\Modules\Integrations\Catalogue} exists separately, as a static list of connectors * that are *known to exist* whether or not they are installed. Keeping the two apart lets the screen * ask whether a PARTICULAR connector is present rather than a vaguer question. */ final class IntegrationRegistry { /** * @var array|null Memoised per request. */ private ?array $integrations = null; /** * Everything registered, keyed by id and sorted by label. * * @return array */ public function all(): array { if ( null !== $this->integrations ) { return $this->integrations; } /** * Filters the connectors available on this site. * * @param IntegrationInterface[] $integrations Connector instances. */ $registered = (array) apply_filters( 'plugixa_crm_integrations', array() ); $found = array(); foreach ( $registered as $integration ) { // A connector that is not one is dropped rather than fatal. This list is assembled from // however many plugins are installed, and one of them returning a string should not take // the Integrations screen — or the REST route behind it — down with it. if ( ! $integration instanceof IntegrationInterface ) { continue; } $id = $integration->id(); if ( '' === $id || isset( $found[ $id ] ) ) { // FIRST REGISTRATION WINS, and the collision is not reported. Two connectors claiming // `woocommerce` is a packaging mistake — the same plugin installed twice, or a fork — // and there is no correct answer to which one owns the credentials already stored // under that id. Taking the first is at least stable across requests, which picking // arbitrarily would not be. continue; } $found[ $id ] = $integration; } uasort( $found, static fn( IntegrationInterface $a, IntegrationInterface $b ): int => strcasecmp( $a->label(), $b->label() ) ); $this->integrations = $found; return $this->integrations; } /** * One connector, or null when nothing provides it. * * @param string $id Machine id. */ public function get( string $id ): ?IntegrationInterface { return $this->all()[ $id ] ?? null; } /** * Whether a connector's code is present on this install. * * @param string $id Machine id. */ public function has( string $id ): bool { return null !== $this->get( $id ); } /** * Drop the memoised list. * * Only tests need this: a single request cannot install a plugin, so within one request the set * of connectors genuinely is fixed. A test that registers a connector after something has already * read the list needs the cache cleared, and having it do so explicitly is better than not * memoising at all and paying for it on every card the screen draws. */ public function flush(): void { $this->integrations = null; } }