# Documentation

FIFA Connect ID PHP SDK, v6.1

# 1. Introduction

The primary responsibility of the FIFA Connect ID Service (FCI) is to generate a unique FIFA_ID for stakeholders involved in football, futsal, and beach soccer. Registration Systems in various Member Associations integrate with FCI to register persons, organisations and facilities in order to obtain FIFA_IDs for these entities.

Integration can be achieved in two ways:

- [Recommended] Via the SDK, a library that encapsulates the REST API and simplifies access to the service.
- Directly, by utilizing the REST API of FCI.

This documentation serves as a reference for developers seeking to integrate Registration Systems with FCI using the SDK.

## 1.1 Chapters overview

Chapter 1 provides introductory information on the classes within the SDK, as well as guidance on dates and thread safety.

Chapter 2 covers the basics of integration with FCI, serving as a starting point for further actions.

The following three chapters discuss the management of specific entity types:

- Chapter 3 describes how to register, update, or merge a Person, with particular attention to identifying national and international duplicates.
- Chapter 4 discusses all aspects of Organisation registration, update, and merge.
- Chapter 5 demonstrates how to register or update a Facility.

Chapter 6 provides information on integration with the Transfer Matching System (TMS), enabling the transmission of data concerning players' first professional registrations, transfers, and committed payments.

Chapter 7 focuses on subscribing to automatic notifications for changes made to entities registered in FCI.

Chapter 8 covers utility tools that can be used for Persons and Organisations data structures creation.

Chapter 9 mentions known issues with PHP SDK utilization.

## 1.2 Clarification on Class Names

Class names, following the FIFA Data Standard, cannot be modified.

## 1.3 Unexpected Setters

Some SDK classes that represent read-only data provided by the server side (e.g., `PersonDuplicateType`) contain setter methods. These are included to allow the client to simulate possible responses from the service, which can be useful for testing.

## 1.4 Date and Time

All SDK methods require date and time in the UTC time zone for consistency across different time zones. If the date/time is not in UTC, an Exception is thrown.

Birth dates should not be affected by time zone changes. For example, both persons born in Chile and Japan on 1.1.2012 should be registered as born on 1.1.2012 0:00 UTC.

> Note: Birth dates returned from the SDK should represent a date without a time zone (time zone should be disregarded when comparing dates). For example, a birth date of 1.1.2012 0:00 [UTC-3] represents the same birth date as 1.1.2012 0:00 [UTC+9].

## 1.5 Thread Safety

Any instance members are not guaranteed to be thread safe. In particular, all methods on `FifaConnectServiceBusClient` cannot be called from multiple threads simultaneously. If needed, a new instance of `FifaConnectServiceBusClient` should be created per thread.

# 2. Setup

## 2.1 PHP version

SDK was built and tested against PHP version 7.1.

## 2.2 Installing via Composer

After downloading zip file from FIFA Confluence into local directory, add an entry `"fifaconnectid/php-sdk": "^6.1.0"` to `require` section in your `composer.json` file, and add new repository type `artifact` in `repositories` section. See `https://getcomposer.org/doc/05-repositories.md#artifact` for more information.

Example:

    {
      "require": {
        "fifaconnectid/php-sdk": "^6.1.0"
      },
      "repositories": [
        {
          "type": "artifact",
          "url": "/path/to/directory/with/zips/"
        }
      ]
    }

## 2.3 Using Registration Facade

The `RegistrationFacade` class simplifies the usage of `FifaConnectIdClient` and `FifaConnectServiceBusClient` in order to seamlessly start executing actions in FCI.

This chapter goes through needed dependencies when bootstrapping the `RegistrationFacade` instance.

### 2.3.1 Connect ID environment

Instance of type `Environment` provides the information about the service SDK will make request to. It is recommended to use one of environments defined as static properties of the class:

    $connectIdEnvironment = Environment::Beta();

Available pre-defined environments are:

Environment | Environment code
--- | ---
Beta | beta
PreProduction | prep
Production | prod

### 2.3.2 Client credentials

In order to authenticate SDK to the service you have to provide a set of client credentials. Create instance of `ClientCredentials` using client ID and secret key gained during given environment onboarding process.

    $clientCredentials = new ClientCredentials('clientId', 'secretKey');

### 2.3.3 Private certificate storage

In order for the SDK to encrypt outgoing and decrypt incoming data, a pair of public and private certificates must be generated.

Instructions on how to generate and upload certificates can be found in fifa-connectservicebus-certificates-generation.html file in root directory of SDK's *.zip package. Once this is done, the next step is to provide the generated private key to the client.

The easiest way to do this is by using the `PrivateCertificateStorageInterface` class. To create an instance of this class, you need to provide `PrivateCertificate` object(s) as its constructor parameter(s). In order to obtain `PrivateCertificate` from **.pem* file, load the key from the generated file and provide an optional password. Please take a look at the following example:

    use Fifa\ConnectId\Sdk\Util\File;
    use Fifa\ConnectServiceBus\Sdk\Encryption\Decrypt\PrivateCertificateMemoryStorage;
    use Fifa\ConnectServiceBus\Sdk\Encryption\Model\PrivateCertificate;

    $filePath = '/path/to/private_key.pem';
    $certificatePassword = "[password]";

    $privateCertificate = new PrivateCertificate(File::getContents($filePath), $certificatePassword);
    $privateCertificateStorage = new PrivateCertificateMemoryStorage($privateCertificate);

In the example above, we used the `PrivateCertificateMemoryStorage` class, which implements the `PrivateCertificateStorageInterface` interface. However, any implementation of `PrivateCertificateStorageInterface` could be used here - we do not restrict using custom code at this point.

### 2.3.4 Logger

Each SDK client method uses an instance `Psr\Log\LoggerInterface` in order to log any errors that might happen during the application runtime. There is no default Logger implementation provided, as different parties could want a different way of storing logs.

    interface LoggerInterface
    {
        /**
         * System is unusable.
         */
        public function emergency($message, array $context = array());

        /**
         * Action must be taken immediately.
         */
        public function alert($message, array $context = array());

        /**
         * Critical conditions.
         */
        public function critical($message, array $context = array());

        /**
         * Runtime errors that do not require immediate action but should typically
         * be logged and monitored.
         */
        public function error($message, array $context = array());

        /**
         * Exceptional occurrences that are not errors.
         */
        public function warning($message, array $context = array());

        /**
         * Normal but significant events.
         */
        public function notice($message, array $context = array());

        /**
         * Interesting events.
         */
        public function info($message, array $context = array());

        /**
         * Detailed debug information.
         */
        public function debug($message, array $context = array());

        /**
         * Logs with an arbitrary level.
         */
        public function log($level, $message, array $context = array());
    }

Each logger method needs to have two parameters provided: `$message` (string) and `$context` (key-value array of strings). A frequent mistake when implementing `Psr\Log\LoggerInterface` is ignoring the `$context` parameter - these keys and values should be logged alongisde the `$message`.

Please take a look at the following example of logger method call:

    $logger->debug('Organisations unmerged with following response', array(
        'organisationType' => $organisationType->__toString(),
        'statusCode' => strval($statusCode),
        'httpHeader' => ToStringConverter::arrayToString($httpHeaders)
    ));

Please make sure that messages will be logged for all levels including level "debug".

Consider inheriting from `Psr\Log\AbstractLogger` which only requires implementation of `public function log($level, $message, array $context = array());`.

### 2.3.5 Registration Facade settings

#### 2.3.5.1 Person Details Storage

Using `PersonDetailsStorageInterface` allows all Registration Systems to exchange persons’ details from their local systems via Service Bus in order to find international duplicates and ensure that the same person will not be registered twice.

If your infrastructure for the application using SDK consists of multiple servers or your listener and registration facade are running in separate processes, then additional configuration is required.

The most important thing to consider when choosing the way you want to host your listener is that it requires a shared storage between all instances of the SDK. Thus, when hosting the listener in the cloud (e.g. as Azure WebJob) or when having multiple instances of Registration System (and in effect: multiple instances of the SDK), each instance must access the same shared state.

The request has to be matched with response. In order for this correlation to work, an ID of the request message (so called Correlation Id) needs to be stored and later retrieved by our code.

> For example, when sending the requests like: `registerPersonAndWaitForDetailsInCaseOfDuplicates` or `getDuplicates` the Correlation Id is created for them. Once the information is back from another Registration System it also contains the same Correlation Id - this way the listener knows which response should be matched with the request.

SDK defines the `PersonDetailsStorageInterface` interface, which must be implemented by the client of the SDK and passed into `RegistrationFacadeSettings`.

    interface PersonDetailsStorageInterface extends CleanableInterface
    {
        /***
         * Puts person details into storage mechanism
         */
        public function put(PersonDetails $personDetails);

        /**
         * Get person details with given correlation id from storage mechanism
         */
        public function getByCorrelationId(string $correlationId): ?PersonDetails;
    }

    interface CleanableInterface
    {
        /**
         * Deletes the entries from the storage that are older than given date
         */
        public function deleteOlderThan(\DateTime $dateTime);
    }

Examples of implementation of `PersonDetailsStorageInterface` are:

- SQL database - shared state is stored in SQL table (MySQL, SQL Server, Oracle)
- cache server - shared state is stored in cache server (e.g. Redis)
- shared disk - shared state is stored as a set of files which are stored on a shared drive (e.g. using Samba protocol)

> FALLBACK: Only when `RegistrationFacade` and listener are running under the same application SDK uses `SqliteStorage` instance correctly. This default implementation of the interface `PersonDetailsStorageInterface` puts XML that is created in the response for Person details request into singleton SQLite database. To assure that it works properly please refer to the SQLite setup manual for your OS.

#### 2.3.5.2 Actor Resolver

You can provide information about the current user interacting with the Registration System. This can be accomplished by providing implementation of `ActorResolverInterface` interface.

    interface ActorResolverInterface
    {
        /**
         * Returns name or identifier of actor
         */
        public function resolve(): string;
    }

Such actor resolver could be much more advanced than `SimpleActorResolver` provided as a default implementation. In case of web application, actor could be fetched from current web request context.

> FALLBACK: `SimpleActorResolver` with empty actor identifier is used by default.

#### 2.3.5.3 Hashed Person Processor

When using local person identifier for Idempotency, you can change its hashing method. This can be done by implementing `HashedPersonProcessorInterface` interface. Provided implementation needs to be passed to `RegistrationFacadeSettings` class when creating registration facade.

    interface HashedPersonProcessorInterface
    {
        /**
         * Process provided hashed person type
         */
        public function process(?HashedPersonType $hashedPersonType): ?HashedPersonType;
    }

> FALLBACK: SHA-512 hashing is used by default.

### 2.3.6 Create Registration Facade

Once dependencies are ready you can create `RegistrationFacade` object:

    $receivingOperationExecutionTimeInSeconds = 60;
    $pollingIntervalInMilliseconds = 50;

    $registrationFacadeSettings = new RegistrationFacadeSettings(
        $personDetailsStorage,                          // 2.3.5.1 Person Details Storage
        $receivingOperationExecutionTimeInSeconds,      // Listener's peek locking timeout in seconds
        $pollingIntervalInMilliseconds,                 // Listener's polling interval in milliseconds
        $actorResolver,                                 // 2.3.5.2 Actor Resolver
        $hashedPersonProcessor                          // 2.3.5.3 Hashed Person Processor
    );

    $registrationFacade = new RegistrationFacade(
        $connectIdEnvironment,          // 2.3.1 Connect ID environment
        $clientCredentials,             // 2.3.2 Client credentials
        $privateCertificateStorage,     // 2.3.3 Private certificate storage
        $logger,                        // 2.3.4 Logger
        $registrationFacadeSettings);   // 2.3.5 Registration Facade settings

## 2.4 Configure listener

After creating facade, you can set up the background listener, which will listen and process new messages received from Connect Service Bus. This is the key element that every Registration System needs to implement.

### 2.4.1 Person Details Provider

When `MessageEventHandler` receives a message with a request for person's details it will call a method `getPersonDetails` on `PersonDetailsProviderInterface` passing FIFA ID of a person.

Therefore you have to provide implementation of `PersonDetailsProviderInterface` that makes a lookup (e.g. in your database) and returns a valid `PersonLocal` XML from FIFA Data Standard.

Chapter 3.1 contains a guide on `PersonLocal` creation and serialization.

If for any reason record for a requested person is not found or cannot be returned, `PersonDetailsProviderInterface` implementation should return `null` or empty string.

    interface PersonDetailsProviderInterface
    {
        /**
         * Gets detailed information about person with given request.
         */
        public function getPersonDetails(PersonDetailsRequest $request): ?string;
    }

XML below is an example of data that should be returned by `PersonDetailsProvider`

    <?xml version="1.0" encoding="UTF-8"?>
    <PersonLocal xmlns="http://fifa.com/fc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xmime="http://www.w3.org/2005/05/xmlmime" PersonFIFAId="105C3Z1" LocalFirstName="ΑΓΓΕΛΗΣ" LocalLastName="ΧΑΡΑΛΑΜΠΟΥΣ" LocalLanguage="gre" LocalCountry="CY" LocalSystemMAId="1234567" InternationalFirstName="ANGELIS" InternationalLastName="CHARALAMBOUS" Gender="male" DateOfBirth="1989-05-31" PlaceOfBirth="Larnaca" RegionOfBirth="Larnaca" CountryOfBirth="CY" Nationality="CY">
        <NationalIdentifier Identifier="GHKJKLD123132132" NationalIdentifierNature="PassportNumber" Country="CY" DateFrom="2010-08-13" DateTo="2020-08-13"/>
        <PlayerRegistration PersonFIFAId="105C3Z1" Status="active" OrganisationFIFAId="105C40I" RegistrationValidFrom="2003-04-11" RegistrationValidTo="2009-06-15" Discipline="Football" Level="amateur" RegistrationNature="Registration"/>
        <PlayerRegistration PersonFIFAId="105C3Z1" Status="active" OrganisationFIFAId="105C40I" RegistrationValidFrom="2009-06-16" Discipline="Football" Level="pro" RegistrationNature="Registration"/>
        <TeamOfficialRegistration PersonFIFAId="105C3Z1" Status="active" OrganisationFIFAId="105C40I" RegistrationValidFrom="2012-08-13" RegistrationValidTo="2014-08-13" Discipline="Futsal" TeamOfficialRole="Coach"/>
    </PersonLocal>

### 2.4.2 Message handlers

You can provide additional message handlers in order to set up additional Service Bus Listener capabilities. Each message handler implements `MessageHandlerInterface` interface.

    interface  MessageHandlerInterface
    {
        /**
         * Checks if handler can handle message of a given message type
         * @param string|null $messageType - message type of the message to handle
         * @return bool - true if message type is supported by handler, otherwise false
         */
        public function canHandle(?string $messageType): bool;

        /**
         * Runs internal handler logic
         * @param string|null $messageType - message type of the message
         * @param Message $message - content of the message
         * @return void
         * @throws XmlDeserializationException
         * @throws ValidationException
         */
        public function handle(?string $messageType, Message $message);
    }

SDK contain few predefined message handlers:

- Custom PersonLocal message handlers
- TMS message handlers
- Notifications handlers

Further chapters will cover their usage, by assuming all of them are added to `messageHandlers` collection, before creating the listener.

    $messageHandlers = array();

### 2.4.3 Create and run listener

In order to create `MessageEventHandler` (listener) instance use previously bootstrapped `RegistrationFacade`:

    $messageEventHandler = $registrationFacade->configureEventHandlers(
        $personDetailsProvider,  // 2.4.1 Person Details Provider
        $messageHandlers);       // 2.4.2 Message handlers

To start listening for messages and handling them run the following method:

    $registrationFacade->receiveMessages();

> IMPORTANT NOTE: Only one listener shall be running at all times.

Service Bus listener script is responsible for communication between your instance of FIFA ConnectId SDK and other SDKs. All requests that are sent from the SDK go into a queue. The other SDKs process those messages and send you responses back. Listener script receives those responses, so they can be stored in database layer (`PersonDetailsStorageInterface`) and then processed by the SDK.

In order to set up background listener, you need to prepare a script and run it cyclically (e.g. by crontab). Script should contain the instance of `RegistrationFacade` and run `receiveMessages()` method on it. It is also required to setup event handlers, just like it was done in previous step.

Note that `configureEventHandlers()` method should be executed only once (not in a loop), as it's adding default handlers to collection of handlers. As a result of this method, you can get an instance of `MessageEventHandlerInterface`, so then you would be able to subscribe with your own listeners to particular events (if necessary).

Example script is placed under `command` folder and named `sample-listener.php`. Suggested setup is to execute it every minute - same timeout value (`$executionTime`) is set in the script.

### 2.4.4 [Recommended] Storage clean-up

When running the listener, incoming data will be put to the shared storage. This database can contain messages that were already processed, as well as messages that - for some reason - will never be processed. Because of that, it is recommended to periodically (e.g. by crontab) run a clean-up of the storage. The sample script that deletes the obsolete data from the storage is located under the `command` directory. It assumes that the storage implements the `CleanableInterface` interface.

    $timestamp = time() - (60 * 60 * 24);
    $dateTime = new \DateTime();
    $dateTime->setTimestamp($timestamp);

    // removal operation
    $storage = SqliteStorage::getInstance($logger); /* or YourSharedStorage */
    $storage->deleteOlderThan($dateTime);

    // Please see section 6.1.2 for reference. If TMS integration is not within the scope of your integration, you can skip this step
    $messagesStorage = MessagesSqliteStorage::getInstance($logger); /* or YourMessagesSharedStorage */
    $messagesStorage->deleteOlderThan($dateTime);

If you opt-in to using a storage of your own, please make sure that you implement the aforementioned interface and within the script replace the SDK' defaults.

### 2.4.5 [Optional] Subscribe to other events from Connect Service Bus

Additionally an instance of the `MessageEventHandler` class exposes the following events:

- `PersonDetailsReceivedEvent`
- `PersonDetailsRequestReceivedEvent`
- `GenericMessageReceivedEvent`
- `ServiceBusExceptionEvent`

These events are automatically raised by the listener. You can use them to register your own event handlers and provide additional logic for handling received messages.

    use Fifa\ConnectId\Sdk\Registration\Event\Handler\EventHandlerInterface;

    // add event handler as instance of Fifa\ConnectId\Sdk\Registration\Event\Handler\EventHandlerInterface
    class TestEventHandler implements EventHandlerInterface
    {
        public function execute($eventData)
        {
            echo 'Message content: ' . $eventData->getContent();
        }
    }

    $messageEventHandler->getPersonDetailsRequestReceivedEvent()->addEventHandler(new TestEventHandler());

    // add event handler as static method
    $messageEventHandler->getPersonDetailsReceivedEvent()->addEventHandler('TestClass::eventHandlerMethod');

### 2.4.6 Retries and dead-letter queue

If a message received from Connect Service Bus is processed properly by all registered handlers (no exceptions are thrown), then the message is marked as processed and deleted from the Connect Service Bus queue.

In case any handler throws an exception, a message will not be deleted from the Connect Service Bus but will be scheduled for later processing. `BackgroundListener` will retry to process this message.

After a series of unsuccessful handles, a message will be moved to a dead messages queue, which will be monitored by FIFA Connect ID Helpdesk.

## 2.5 Using FIFA Connect ID Client

### 2.5.1 Get client via Registration Facade

Note that in most cases you would need to use Registration Facade instance, not the client. It is, however possible to get FIFA Connect ID Client out of facade as shown in below example.

    $fifaConnectIdClient = $registrationFacade->getConnectIdClient();   // 2.3 Using Registration Facade

### 2.5.2 Create instance

You can also create a standalone `FifaConnectIdClientImpl` by injecting its dependencies into public constructor:

    $fifaConnectIdClient = new FifaConnectIdClientImpl(
        $connectIdEnvironment, // 2.3.1 Connect ID environment
        $clientCredentials,    // 2.3.2 Client credentials
        $logger);              // 2.3.4 Logger

## 2.6 [Optional] Configure caching

As application uses cache files, it is possible to set the directory path for a place where they can be stored. By default SDK puts files into `.cache` folder located in a root folder of Service Bus SDK library. To change this value provide an `EnvironmentSettings` instance when creating `Environment` object. More details can be found in Service Bus SDK documentation.

# 3. Person

The first section 3.1 guides through working with PersonLocal, introducing XML serialization and validation. These are particularly useful in `PersonDetailsProviderInterface`, when responding the other organisation with person details via Service Bus.

Current version of the SDK supports two approaches to operations on Person entity:

- using Registration Facade to register/update/find a person and search for duplicates at the same time. Due to personal data protection regulation, providing details of persons from other Registration Systems requires communication with Hashing Service and Connect Service Bus. Registration Facade hides this complexity behind a single method.
- using FIFA Connect ID Client for other scenarios (including force registration or registrations management).

Two following sections describe implementation steps which are necessary to cover both scenarios:

- Chapter 3.2 - Person registration/update/search together with a duplicate check
- Chapter 3.3 - operations on Person entity not requiring a duplicate check

The last section 3.4 covers rarely used custom message type person details sharing via Service Bus.

## 3.1 Working with PersonLocal

### 3.1.1 Create PersonLocal object

SDK doesn't restrict any method of generating string with XML content. Recommended and by far the easiest method, however, is using `PersonLocalXmlSerializer` class, that we provided in SDK. All that is required is to run the method `serialize()` which takes `PersonLocal` object as an input. When creating an instance of `PersonLocal`, all mandatory fields are required in class constructor. Optional fields are available to set by modifying public properties. This rule applies for all model classes used in XML serialization process. Check below example to see how to create `PersonLocal` object with required and optional fields.

    use Fifa\ConnectId\Sdk\Model\DataStandard\PersonLocal;
    use Fifa\ConnectId\Sdk\Util\Data\Gender;

    // required fields
    $localLastName = 'ΧΑΡΑΛΑΜΠΟΥΣ';
    $gender = Gender::MALE;
    $nationality = 'CY';
    $dateOfBirth = new \DateTime('1989-05-31', new \DateTimeZone('UTC'));
    $countryOfBirth = 'CY';
    $placeOfBirth = 'Larnaca';
    $localLanguage = 'gre';
    $localCountry = 'CY';
    $personFIFAId = '105C3Z1';

    // creating an instance of the object
    $personLocal = new PersonLocal(
        $localLastName, $gender, $nationality, $dateOfBirth, $countryOfBirth, $placeOfBirth, $localLanguage, $localCountry, $personFIFAId
    );

    // optional fields
    $personLocal->LocalFirstName = 'ΑΓΓΕΛΗΣ';
    $personLocal->LocalSystemMAId = '1234567';
    $personLocal->InternationalFirstName = 'ANGELIS';
    $personLocal->InternationalLastName = 'CHARALAMBOUS';
    $personLocal->RegionOfBirth = 'Larnaca';
    $personLocal->PopularName = 'Angelis Angeli';
    $personLocal->SecondNationality = 'GR';

`PersonLocal` created this way contains general information of the person whose details will be sent to other Registration System. Another business context are registrations. Each registration type can be included in XML file by adequate method: `addPlayerRegistration()`, `addTeamOfficialRegistration()`, `addMatchOfficialRegistration()` and `addOrganisationOfficialRegistration()`. As all types are very similar to each other (as all extend `Registration` abstract class), let's focus on how to set up `PlayerRegistration` and link it with `PersonLocal` object:

    use Fifa\ConnectId\Sdk\Model\DataStandard\PlayerRegistration;
    use Fifa\ConnectId\Sdk\Util\Data\Discipline;
    use Fifa\ConnectId\Sdk\Util\Data\Person\Level;
    use Fifa\ConnectId\Sdk\Util\Data\RegistrationNature;
    use Fifa\ConnectId\Sdk\Util\Data\Status;

    // required fields
    // PersonDetailsRequest is available in getPersonDetails() method of PersonDetailsProviderInterface interface
    $personFIFAId = $request->getUniqueFifaId();
    $status = Status::ACTIVE;
    $clubOrMemberAssociationId = '105C40I';
    $registrationValidFrom = new \DateTime('2003-04-11', new \DateTimeZone('UTC'));
    $level = strtolower(Level::AMATEUR);
    $discipline = Discipline::FOOTBALL;
    $registrationNature = RegistrationNature::REGISTRATION;

    // creating an instance of the object
    $playerRegistration = new PlayerRegistration(
        $personFIFAId, $status, $clubOrMemberAssociationId, $registrationValidFrom, $level, $discipline, $registrationNature
    );

    // optional fields
    $playerRegistration->setRegistrationValidTo(new \DateTime('2009-06-15', new \DateTimeZone('UTC')));
    $playerRegistration->setClubTrainingCategory(ClubTrainingCategory::CATEGORY_3);

    // include player registration in PersonLocal
    $personLocal->addPlayerRegistration($playerRegistration);

`PersonLocalXmlSerializer` reads string properties and parses them into XML tags or attributes. Because of that, all model classes use `string` instead of `DateTime` objects for fields which are storing dates. While it's possible to write values to public properties directly, we recommend to use setter methods that we provide in such cases. An example of such usage is `setRegistrationValidTo()` method in above example.

Another important field, which can be set in `PersonLocal` in similar way as registrations, is `Photo`. This property itself is not required, but when using it, a mandatory instance of `PictureEmbedded` class needs to be passed as a constructor argument. SDK gives two possibilities when preparing `PictureEmbedded` object: using base64 encoded string as an attribute (`createFromBase64String()`) or using path to local file (`createFromLocalPath()`). The last option, reads file from your local storage, encodes it to base64 and then uses `createFromBase64String()` under the hood. Below example shows what's the approach here:

    use Fifa\ConnectId\Sdk\Model\DataStandard\Photo;
    use Fifa\ConnectId\Sdk\Model\DataStandard\PictureEmbedded;

    // creating an instances of objects
    $pictureEmbedded = PictureEmbedded::createFromLocalPath($absolutePathToPhoto);
    $photo = new Photo($pictureEmbedded);

    // include photo in PersonLocal
    $personLocal->Photo = $photo;

SDK supports following image formats: jpg, png, gif, tif, bmp. Please mind that the maximum size of a person details message sent through Service Bus is approximately 10MB. You might want to ensure that the size of the photo does not make the entire PersonLocal object exceed that limit. A reasonable boundary for a PNG file is 8MB.

`NationalIdentifier` and `LocalPersonName` are both optional in `PersonLocal` scope and there is no difference in rules with setting their properties comparing to other fields. See example of `NationalIdentifier` below:

    use Fifa\ConnectId\Sdk\Api\Model\NationalIdentifierType;
    use Fifa\ConnectId\Sdk\Model\DataStandard\NationalIdentifier;

    // required fields
    $identifier = '9876543210';
    $nationalIdentifierNature = NationalIdentifierType::NATIONAL_IDENTIFIER_NATURE_PASSPORT_NUMBER;
    $country = 'CY';

    // creating an instance of the object
    $nationalIdentifier = new NationalIdentifier($identifier, $nationalIdentifierNature, $country);

    // optional fields
    $nationalIdentifier->Description = 'NationalIdentifier test description';
    $nationalIdentifier->setDateFrom(new \DateTime('2003-04-11', new \DateTimeZone('UTC')));
    $nationalIdentifier->setDateTo(new \DateTime('2004-06-01', new \DateTimeZone('UTC')));

    // include NationalIdentifier in PersonLocal
    $personLocal->NationalIdentifier = $nationalIdentifier;

### 3.1.2 Serialize XML document

> IMPORTANT NOTE: Before using XML serialization, make sure that your project (`composer.json`) includes following package:

>     "pear/xml_serializer": "0.22.*"

After `PersonLocal` object is created, you need to pass it to `serialize()` method of `PersonLocalXmlSerializer` service. Under the hood, this method creates XML string and executes validation against FIFA Data Standard. In case validation fails, `XmlSerializationException` will be thrown giving a message with all possible issues that were found during validation. As all of this happens in background listener, you should implement a proper error handling when catching the exception, so to get notified in case when XML response was not generated. Note that in case of exception thrown, Registration System that was asking for person details, won't receive a correct answer, so it's important to keep data structure valid and try to limit number of exceptions.

    use Fifa\ConnectId\Sdk\Exception\XmlSerializationException;
    use Fifa\ConnectId\Sdk\Util\PersonLocalXmlSerializer\PersonLocalXmlSerializer;

    $personLocalXmlSerializer = new PersonLocalXmlSerializer();
    $xmlString = null;
    // PersonDetailsRequest is available in getPersonDetails() method of PersonDetailsProviderInterface interface
    $personFIFAId = $request->getUniqueFifaId();

    try {
        $xmlString = $personLocalXmlSerializer->serialize($personLocal);
    }
    catch (XmlSerializationException $e) {
        // handle serialization exception
        $logger->error(sprintf('Unable to serialize XML for person: %s. Details: %s', $personFIFAId, $e->__toString()));
    }

### 3.1.3 Deserialize XML with person details

When running operations that could possibly return person’s duplicates (such as registering persons, updating persons, etc.), methods will return responses with array of `PersonDuplicateWithDetails`. `PersonDetails` that have been received here contains an XML content, which is available in `getXmlData()` method. At this point application can store all details in local database (or handle them in a different way - depending on a need), so that it is possible to compare person being processed with their potential duplicates.

Optionally, after receiving XML string from `getXmlData()`, you may parse it into object. This can be done with a custom implementation, however the suggested solution here is to use `PersonLocalXmlSerializer` class (provided by our SDK) and its `deserialize()` method returning `PersonLocal` object. Take a look at the following example:

    $personLocalXmlSerializer = new PersonLocalXmlSerializer();

    try {
        $personLocal = $personLocalXmlSerializer->deserialize($personDuplicateWithDetails->getPersonDetails()->getXmlData());
    }
    catch (XmlDeserializationException $e) {
        $xmlContent = $e->getXml();
        $innerException = $e->getPrevious();
    }

### 3.1.4 [Optional] XML validation

As `PersonLocalXmlSerializer` includes XML validation, there is no need of running additional validation over it. But, in case of internal unit tests or when instead of our helper tool, custom solution produces XML content, it is possible to check whether output is compliant with FIFA Data Standard. To achieve that, use `XsdSchemaValidator` as on following example:

    use Fifa\ConnectId\Sdk\Util\XsdSchemaValidator\XsdSchemaValidator;

    $xsdSchemaValidator = new XsdSchemaValidator();
    $validationResult = $xsdSchemaValidator->validate($xmlString);

    if (!$validationResult->isValid()) {
        // handle validation errors
        $logger->error('Document is not compliant with FIFA Data Standard. Check errors for more details.',
            array('errors' => implode('; ', $validationResult->getErrors())));
    }

## 3.2 Working with Registration Facade

The `RegistrationFacade` class covers full scenario of registering a person in FIFA Connect ID. If potential duplicates are found, Registration Facade will send requests for person details to other Registration Systems using Connect Service Bus and wait for their response (with timeout). The same logic needs to apply when one tries to update Person details or search for duplicates but does not intend to register a person even if no duplicates are found.

### 3.2.1 Register new person

First create a person you want to register:

    use Fifa\ConnectId\Sdk\Api\Model\PersonType;
    use Fifa\ConnectId\Sdk\Api\Model\PlayerRegistrationType;
    use Fifa\ConnectId\Sdk\Model\PersonData;
    use Fifa\ConnectId\Sdk\Model\PersonName;
    use Fifa\ConnectId\Sdk\Util\Data\Discipline;
    use Fifa\ConnectId\Sdk\Util\Data\Person\Level;
    use Fifa\ConnectId\Sdk\Util\Data\RegistrationNature;
    use Fifa\ConnectId\Sdk\Util\Data\Status;

    // setup player name(s)
    $personNames = array();
    $personNames[] = new PersonName('John', 'Doe');

    // setup birth date
    $person = new PersonType();
    $person->setDateOfBirth(new \DateTime('1990-04-26', new \DateTimeZone('UTC')));
    $person->setGender('male');

    // setup registrations
    $registration = new PlayerRegistrationType();
    $registration->setOrganisationFifaId($organisationId);
    $registration->setStatus(Status::ACTIVE);
    $registration->setLevel(Level::PRO);
    $registration->setDiscipline(Discipline::FOOTBALL);
    $registration->setRegistrationNature(RegistrationNature::REGISTRATION);

    $person->setPlayerRegistrations(array($registration));

    // finally create PersonData
    $personData = new PersonData($person, $personNames);

Then try to register a person using Registration Facade. The code snippet below shows an example handling (writing to console) of both scenarios, i.e. successful registration and potential duplicates found.

    /* A record identifier in your database or any other key that is unique for a person.
    This data will be stored hashed and cannot be decrypted.
    For further information please consult section 3.2.1.1 Idempotency */
    $personLocalId = '';
    $timeoutInSeconds = 20;

    $result = $registrationFacade->registerPersonAndWaitForDetailsInCaseOfDuplicatesByGivingPersonData($personData, $timeoutInSeconds, $personLocalId);
    if ($result->isSuccessful()) {
        echo 'Person successfully registered with unique FIFA ID: ' . $result->getUniqueFifaId() . '.';
    }
    else {
        echo 'Duplicates found.';

        foreach ($result->getPersonDuplicateWithDetails() as $duplicateWithDetails) {

            $idOfDuplicatedPerson = $duplicateWithDetails->getDuplicate()->getPersonFifaId();
            $proximityScore = $duplicateWithDetails->getDuplicate()->getProximityScore();
            $dataReceived = is_null($duplicateWithDetails->getPersonDetails()) ?
                '<detailed person data not received from other Registration System - timeout>' :
                // note that getXmlData() method returns raw XML data, so tags won't be visible when displaying in a browser
                $duplicateWithDetails->getPersonDetails()->getXmlData();

            echo 'FIFA ID: ' . $idOfDuplicatedPerson . ', score: ' . $proximityScore . ', FIFA Data XML: ' . $dataReceived;
        }
    }

Service Bus listener must be up and running to receive person details. There may be cases where a person has no registrations, but still appears on the list of duplicates as FIFA Data Standard does not require any registrations to construct a valid object. Connect ID stores computed hashes, but has no way of knowing which Registration System to ask for the details, so as a result null XML will be returned. In addition to that, for every potential duplicate there will be a hash score computed, ranging from 0 (no resemblance) to 1 - exact match. Your system should be prepared to handle duplicates, considering both the possibility of receiving incomplete data and the value of the hash score.

#### 3.2.1.1 Idempotency

Idempotency ensures that the same record is saved only once even when multiple registration requests are sent. To ensure that the same record is not registered in the system twice we recommend to pass person local identifier when registering person (Person local id can be a record identifier in your database or any other key that is unique for a person). Local identifier will be useful in case of retries on the client side (e.g. due to network issues or occasional client time-outs). It is sent and stored on the server in a hashed form (SHA-512 is used by default by SDK) and cannot be decrypted.

If you choose not to take advantage of the idempotency mechanism, you should expect that in rare situations FIFA_ID will be created on/by the server but you will not receive it on the client. Should this happen, the next time you try to register the same person, you will receive a duplicate pointing to your Registration System, but without any details (as your listener will not be able to provide details for the person which FIFA_ID you have not received).

The only thing that you need to do to start using idempotency mechanism is to pass additional parameter (local person identifier) to `forceRegisterPerson` or `registerPersonAndWaitForDetailsInCaseOfDuplicatesByGivingPersonData` methods on `RegistrationFacade`. Based on the method that is used the system behaves as follows:

- when using `registerPersonAndWaitForDetailsInCaseOfDuplicatesByGivingPersonData`
  Take a look at the following example:

    /** @var string $personLocalId */
    $personLocalId = "Person Local Id";

    $registrationFacade->registerPersonAndWaitForDetailsInCaseOfDuplicatesByGivingPersonData($personData, $timeoutInSeconds, $personLocalId);

In the example above, the `registerPersonAndWaitForDetailsInCaseOfDuplicatesByGivingPersonData` will check if there is no other registration with the same person local identifier. If the registration with the same person local identifier exists, it will be returned as a result. There will be no second registration nor the update of an existing one. If there is no registration with the same person local identifier, the method will run the default logic of searching for duplicates and registering a new person. Person local identifier will be stored on the server in the hashed form for the newly created record.

Please note, that the use of the `forceRegisterPerson` does not change the behaviour, i.e. a new person will not be created if the same person local identifier is used.

#### 3.2.1.2 Handling multiple names (local, international and maiden/birth names)

SDK provides support for registering a person with multiple names. Two scenarios will usually require such a feature:

- registering a person for which local spelling differs from international spelling (e.g. from a non-Latin alphabet country)
- registering a person with both last name and maiden name

Support for such a scenario depends on the way person registration is used.

Before registering a person, you need to create an array of pairs of names. The following example shows the most sophisticated scenario when a single person has both: local name, international name and maiden name provided. Although two pairs of names will be used in minority of cases and three pairs hardly ever, the example is instructive.

    use Fifa\ConnectId\Sdk\Model\PersonName;

    $internationalFirstName = "Olga";
    $internationalLastName = "Terekhova";
    $localFirstName = "Ольга";
    $localLastName = "Терехова";
    $localMaidenName = "Бессолова";

    $personInternationalName = new PersonName($internationalFirstName, $internationalLastName);
    $personLocalName = new PersonName($localFirstName, $localLastName);
    $personMaidenName = new PersonName($localFirstName, $localMaidenName);

    // setup player name(s)
    $personNames = array(
        $personInternationalName,
        $personLocalName,
        $personMaidenName
    );

#### 3.2.1.3 Troubleshooting

Please keep in mind that, especially in the Beta environment, some of the listeners of other Registration Systems might not be working. Methods relying on communication with other Registration Systems' listeners will wait for a specified period of time for the message from them to arrive and will return without them afterwards.

If you are certain that both your and the other Registration System's listener are up and running and you still do not receive person details, make sure that a valid implementation of `PersonDetailsStorageInterface` is used. More on the subject can be found in the chapter Person details storage.

Detailed explanation of handling aforementioned issues can be found under this FAQ article.

### 3.2.2 Update person

First create two persons and register them:

    use Fifa\ConnectId\Sdk\Api\Model\PersonType;
    use Fifa\ConnectId\Sdk\Api\Model\PlayerRegistrationType;
    use Fifa\ConnectId\Sdk\Model\PersonData;
    use Fifa\ConnectId\Sdk\Model\PersonName;
    use Fifa\ConnectId\Sdk\Util\Data\Discipline;
    use Fifa\ConnectId\Sdk\Util\Data\Person\Level;
    use Fifa\ConnectId\Sdk\Util\Data\RegistrationNature;
    use Fifa\ConnectId\Sdk\Util\Data\Status;

    // setup player name(s)
    $person1Names = array(new PersonName('John', 'Doe'));
    $person2Names = array(new PersonName('John', 'Kowalsky'));

    // setup birth date
    $person = new PersonType();
    $person->setDateOfBirth(new \DateTime('1990-04-26', new \DateTimeZone('UTC')));

    // setup registrations
    $registration = new PlayerRegistrationType();
    $registration->setOrganisationFifaId($organisationId);
    $registration->setStatus(Status::ACTIVE);
    $registration->setLevel(Level::PRO);
    $registration->setDiscipline(Discipline::FOOTBALL);
    $registration->setRegistrationNature(RegistrationNature::REGISTRATION);

    $person->setPlayerRegistrations(array($registration));

    // create PersonData
    $person1Data = new PersonData($person, $person1Names);
    $person2Data = new PersonData($person, $person2Names);

    // register two different players
    $person1Id = $registrationFacade->getConnectIdClient()->getPersonApi()->forceRegister($person1Data);
    $person2Id = $registrationFacade->getConnectIdClient()->getPersonApi()->forceRegister($person2Data);

Then try to update second person using Registration Facade with names of first person. The code snippet below shows an example handling (writing to console) of both scenarios, i.e. successful update and potential duplicates found. Please note that this operation will throw an `InvalidClientDataException` if you lack the permissions to perform it (the player has active registrations, none of which belongs to your Registration System).

    $timeoutInSeconds = 10;
    $result = $registrationFacade->updatePersonAndWaitForDetailsInCaseOfDuplicates($person2Id, $timeoutInSeconds, $person1Names, $dateOfBirth, $gender);

    if ($result->isSuccessful()) {
        echo 'Person updated successfully.';
    }
    else {
        echo 'Duplicates found.';

        foreach ($result->getPersonDuplicateWithDetails() as $duplicateWithDetails) {

            $idOfDuplicatedPerson = $duplicateWithDetails->getDuplicate()->getPersonFifaId();
            $proximityScore = $duplicateWithDetails->getDuplicate()->getProximityScore();
            $dataReceived = is_null($duplicateWithDetails->getPersonDetails()) ?
                '<detailed person data not received from other Registration System - timeout>' :
                $duplicateWithDetails->getPersonDetails()->getXmlData();

            echo 'FIFA ID: ' . $idOfDuplicatedPerson . ', score: ' . $proximityScore . ', FIFA Data XML: ' . $dataReceived;
        }
    }

### 3.2.3 [Optional] Get person duplicates (without registering)

If you need to get list of duplicates for a person, method `getDuplicates` can be used. This methods looks for duplicates in Connect ID service. If any duplicate is found requests for person details are sent to relevant Registration Systems. It is possible to look for duplicates only within specific organisations by providing `organisationsIds` argument.

    $foundDuplicates = $registrationFacade->getDuplicates($personData, 10);

    if (sizeof($foundDuplicates) > 0) {

        echo 'Duplicates found.';

        foreach ($foundDuplicates as $duplicateWithDetails) {

            $idOfDuplicatedPerson = $duplicateWithDetails->getDuplicate()->getPersonFifaId();
            $proximityScore = $duplicateWithDetails->getDuplicate()->getProximityScore();
            $dataReceived = $duplicateWithDetails->getPersonDetails() == null
                ? "<detailed person data not received from other Registration System - timeout>"
                // note that getXmlData() method returns raw XML data, so tags won't be visible when displaying in a browser
                : $duplicateWithDetails->getPersonDetails()->getXmlData();

            echo sprintf('FIFA ID: %s, score: %s, FIFA Data XML: %s', $idOfDuplicatedPerson, $proximityScore, $dataReceived);
        }
    }

#### 3.2.3.1 [Optional] Get person duplicates by person FIFA ID

To get the list of duplicates for a registered person, use method `getDuplicatesExceptTrustedKnownDuplicates`. To ignore duplicates marked as known duplicate by trusted organisations, please provide optional `trustedOrganisationsIds` argument.

    $personFifaId = "105C40T";
    $registrationFacade->getDuplicatesExceptTrustedKnownDuplicates($personFifaId, 10 /** timeout in seconds */);

### 3.2.4 Known limitations of Registration Facade

#### Available operations

Registration Facade covers scenario of person registration/update/search, but for other functionalities FIFA Connect ID Client described in the next chapter must be used.

In particular FIFA Connect ID Client must be used for:

- force registration of a person
- getting person data from FIFA Connect ID Directory (with hashed names)
- all operations on organisation and facility

#### Concurrent requests

Currently there is no request throttling in API, but it's recommended not to exceed 10 concurrent connections. Too many requests to API may result in requests time outs. In case you plan to do a batch import of person records, please contact support team so that the infrastructure can be scaled up accordingly.

## 3.3 Working with FIFA Connect ID Client

This chapter discusses all operations on a Person entity, e.g. Registration, Update, Merge, etc. It is different from chapter 3.2 Working with Registration Facade in that operations described here do not involve a duplicate check. A developer who wants to perform a duplicate check via Service Bus should either refer to chapter 3.2 or implement communication via Service Bus by themselves.

### 3.3.1 Registering a person

In order to register a person in FIFA Connect ID service, provide a `PersonData` object. An example:

    use Fifa\ConnectId\Sdk\Api\Model\PersonType;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\FifaDuplicatedPersonFoundException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\Model\PersonData;
    use Fifa\ConnectId\Sdk\Model\PersonName;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    try {
        $personType = new PersonType();
        $personType->setDateOfBirth(new \DateTime('1990-04-26', new \DateTimeZone('UTC')));
        // fill up registrations

        $personData = new PersonData($personType, new PersonName('Firstname', 'Lastname'));

        /* A record identifier in your database or any other key that is unique for a person.
        This data will be stored hashed and cannot be decrypted.
        For further information please consult section 3.2.1.1 Idempotency */
        $personLocalId = '';

        $fifaId = $fifaConnectIdClient->getPersonApi()->register($personData, $personLocalId);
    }
    catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    }
    catch (FifaDuplicatedPersonFoundException $e) {
        $duplicates = $e->getDuplicates();
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e){
        // handle exception
        $headers = $e->getResponseHeaders();
        $body = $e->getResponseBody();
        $object = $e->getResponseObject();
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

#### 3.3.1.1 Person bulk registration

In order to register a bulk number of persons, prepare an array with person data and run registration process in a loop. Please note that in order to optimize costs of test environment we keep it small-scale. Let us know upfront if you plan to test bulk registration, so we can scale up the environment. Additionally we recommend not exceeding 20 concurrent registrations to avoid performance degradation of the service. Take a look at the following example:

    $database = new LocalDatabase();
    $players = $database->getNotRegisteredPlayers();

    foreach ($players as $player) {

        $personData = $this->createPersonData($player);
        $fifaUniqueId = $fifaConnectIdClient->getPersonApi()->register($personData);
        $database->updateFifaUniqueId($player, $fifaUniqueId);
    }

Even though there is no support for multi-threading in PHP, it's still possible to bulk register players using multi-processing. Suggested solution is to divide the collection of players into smaller chunks, so each process could work on different set of data. Such processes should work in parallel. Examples on how to set up parallel processing on different operating systems can be found in folder `command\BulkRegistration`.

- `multiprocess-registration.bat` script is configured to run on Windows environments
- `multiprocess-registration.sh` shows how to setup multiprocess registration on Unix based system.

Note that above scripts are just an example, therefore sample code doesn't contain full implementation of registration process.

### 3.3.2 Force registration

To force the registration process (proceed even if potential duplicates are found):

    use Fifa\ConnectId\Sdk\Api\Model\PersonType;
    use Fifa\ConnectId\Sdk\Environment\Environment;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\FifaConnectIdClient;
    use Fifa\ConnectId\Sdk\Model\PersonData;
    use Fifa\ConnectId\Sdk\Model\PersonName;
    use Fifa\ConnectServiceBus\Sdk\Authentication\Model\ClientCredentials;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    try {
        $personType = new PersonType();
        $personType->setDateOfBirth(new \DateTime('1990-04-26', new \DateTimeZone('UTC')));
        // fill up registrations

        $personData = new PersonData($personType, new PersonName('Firstname', 'Lastname'));

        /* A record identifier in your database or any other key that is unique for a person.
        This data will be stored hashed and cannot be decrypted.
        For further information please consult section 3.2.1.1 Idempotency */
        $personLocalId = '';

        $fifaId = $fifaConnectIdClient->getPersonApi()->forceRegister($personData, $personLocalId);
    }
    catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle exception
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

### 3.3.3 Getting person data

This method will throw `Fifa\ConnectId\Sdk\Exception\FifaPersonMergedException` when you try to get data for a person that has been previously merged (refer to section 3.3.4 Merging/unmerging persons). The exception will contain data about requested person together with primary person id. To get the information associated with the specified FIFA ID such as a date of birth or list of person registrations:

    use Fifa\ConnectId\Sdk\Api\Model\PersonType;
    use Fifa\ConnectId\Sdk\Environment\Environment;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\FifaEntityDeletedException;
    use Fifa\ConnectId\Sdk\Exception\FifaEntityNotFoundException;
    use Fifa\ConnectId\Sdk\Exception\FifaPersonMergedException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\FifaConnectIdClient;
    use Fifa\ConnectServiceBus\Sdk\Authentication\Model\ClientCredentials;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    try {
        $fifaId = '1000G88';
        /** @var PersonType $personType */
        $personType = $fifaConnectIdClient->getPersonApi()->getPersonData($fifaId);

        $dateOfBirth = $personType->getDateOfBirth();
        $playerRegistrations = $personType->getPlayerRegistrations();
    }
    catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    }
    catch (FifaEntityNotFoundException $e) {
        // could not find the person
    }
    catch (FifaEntityDeletedException $e) {
        // person has been deleted
    }
    catch (FifaPersonMergedException $e) {
        // requested person has been merged
        $person = $e->getRequestedPerson();
        $primaryPersonId = $e->getPrimaryPersonId();
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle exception
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

### 3.3.4 Merging/unmerging persons

To merge two persons into a single one use `mergePersons` method available on `PersonApi`. As a result, the primary person data will be kept. No more operations should be executed on the secondary person. Attempt to get secondary person's data will result in 301 error code. Please refer to section 3.3.3 Getting person data. This operation can end with conflict error under specific conditions. It can happen when previous merge/unmerge operation is not yet completed on the same persons. As a result `FifaConflictException` will be thrown and client should retry operation later.

Here is an example how to merge persons:

    use Fifa\ConnectId\Sdk\Exception\FifaConflictException;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\FifaEntityNotFoundException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\Exception\UnauthorizedException;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    try {
        $person = $fifaConnectIdClient->getPersonApi()->mergePersons($primaryPersonId, $secondaryPersonId);
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (InvalidClientDataException $e) {
        // data sent to the service was invalid
        $details = $e->getBadRequestResponse();
    }
    catch (FifaConflictException $e) {
        // a conflicting operation is currently in progress
        $details = $e->getConflictResponseType();
    }
    catch (FifaEntityNotFoundException $e) {
        // could not find the person
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle exception
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

In order to revert merge operation call `unmergePersons` method with the same arguments.

#### 3.3.4.1 Getting merged secondary FIFA IDs for person

To retrieve all merged secondary FIFA IDs for a specific person, use the `getMergedSecondaryFifaIdsForPerson` method provided by the `PersonApi`.

Note that only persons directly merged into the provided FIFA ID will be considered (those that can be unmerged).

    $personFifaId = "";

    try {
        $personSecondaryFifaIds = $fifaConnectIdClient->getPersonApi()->getMergedSecondaryFifaIdsForPerson($personFifaId);
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (InvalidClientDataException $e) {
        // data sent to the service was invalid
        $details = $e->getBadRequestResponse();
    }
    catch (FifaEntityNotFoundException $e) {
        // could not find the person
    }
    catch (FifaEntityDeletedException $e) {
        // person has been deleted
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle exception
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

### 3.3.5 Getting duplicates

To get a list of people a person may be duplicated with:

    use Fifa\ConnectId\Sdk\Api\Model\PersonDuplicateType;
    use Fifa\ConnectId\Sdk\Api\Model\PersonType;
    use Fifa\ConnectId\Sdk\Environment\Environment;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\FifaConnectIdClient;
    use Fifa\ConnectId\Sdk\Model\PersonData;
    use Fifa\ConnectId\Sdk\Model\PersonName;
    use Fifa\ConnectServiceBus\Sdk\Authentication\Model\ClientCredentials;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    $personType = new PersonType();
    $personType->setDateOfBirth(new \DateTime('1990-04-26', new \DateTimeZone('UTC')));
    // fill up registrations
    $personData = new PersonData($personType, new PersonName('Firstname', 'Lastname'));

    try {
        /** @var PersonDuplicateType[] $potentialDuplicates */
        $potentialDuplicates = $fifaConnectIdClient->getPersonApi()->getDuplicates($personData);
    }
    catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle exception
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

This method is expected to be used rarely, e.g. in cases when Registration System chooses to do a duplicate check without the registration.

It is possible to look for duplicates only within specific organisations by providing `organisationsIds` argument.

#### 3.3.5.1 Getting duplicates by FIFA identifier

To get a list of people a registered person may be duplicated with:

    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    $personFifaId = '105C408';

    try {
        $duplicates = $fifaConnectIdClient->getPersonApi()->getDuplicatesExceptTrustedKnownDuplicates($personFifaId);
    }
    catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle exception
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

To ignore known duplicates registered by specific organisations, please provide optional `trustedOrganisationsIds` argument.

### 3.3.6 Add/Update registrations

When registering a person for the first time we need to provide an object of `PersonType` class. It is also possible to update an existing record (provided that we know the person's FIFA ID) by adding or editing its registrations. In order to do that, use the class `AddRegistrationsRequestType` or `UpdateRegistrationsRequestType` (depending on the operation) and fill information about the registrations. Note, that each person always contains four collections of registrations:

- player registrations
- match official registrations
- team official registrations
- organisation official registrations

In the examples below, only player registrations are used - the other 3 collections remain empty (they are not set).

> Important note: Operation that results in Member Association (OrganisationFifaId) being changed for a given registration constitutes a transfer in Connect ID. Only one transfer at the same time is allowed. If you want to perform multiple transfers, please call `addRegistrations` or `updateRegistrations` methods separately for each transfer.

#### 3.3.6.1 Add registrations

Note that a registration is added in a National Association and not in a Club. This is not a coincidence - FIFA Connect ID Service, although allowed to store Clubs in general, is not allowed to store persons' assignments to Clubs. Server will return an error should the Client try to register a person within a Club. There are several other rules applied here,

    use Fifa\ConnectId\Sdk\Api\Model\AddRegistrationsRequestType;
    use Fifa\ConnectId\Sdk\Api\Model\PersonRegistrationsType;
    use Fifa\ConnectId\Sdk\Api\Model\PlayerRegistrationType;
    use Fifa\ConnectId\Sdk\Util\Data\RegistrationNature;

    $myPersonFifaId = '16N9M75';
    $organisationId = '1007UDF';

    // this array contains a single collection of player (not person) registrations...
    $playerRegistrations = array();
    // ... and this one has 4 collections or registrations, including player registrations
    $personRegistration = new PersonRegistrationsType();
    $request = new AddRegistrationsRequestType();

    // fill up registrations
    $exampleRegistration = new PlayerRegistrationType();
    $exampleRegistration
        ->setDiscipline('Futsal')
        ->setStatus('active')
        ->setLevel('Amateur')
        ->setOrganisationFifaId($organisationId)
        ->setRegistrationNature(RegistrationNature::LOAN);

    // add a new registration to a collection of registrations
    $playerRegistrations[] = $exampleRegistration;

    // add player registrations (single collection, single item in our code) as one of the 4 collections in person registrations
    $personRegistration->setPlayerRegistrations($playerRegistrations);

    // prepare request
    $request->setPersonId($myPersonFifaId)->setRegistrations($personRegistration);

    $result = $fifaConnectIdClient->getPersonApi()->addRegistrations($request, false);

#### 3.3.6.2 Update registrations

In order to execute a request we need to know the registrations we want to update and their new values. Note that the order of registrations is important. Operation will try to change the first item in "old" list with the first item in the "new" list, etc.

"Old" list should contain only those registrations, which you want to update. For example, when person has four registrations and you want to update only two of them, you just need to provide these two.

    use Fifa\ConnectId\Sdk\Api\Model\PersonRegistrationsType;
    use Fifa\ConnectId\Sdk\Api\Model\PlayerRegistrationType;
    use Fifa\ConnectId\Sdk\Api\Model\UpdateRegistrationsRequestType;

    $myPersonFifaId = '16N9M75';
    $organisationId = '1007UDF';

    $updateRegistration1 = new PlayerRegistrationType();
    $updateRegistration2 = new PlayerRegistrationType();
    // collection of player registrations (one of the 4 collections of person registrations)
    $playerOldRegistrations = array();
    $playerNewRegistrations = array();
    $personOldRegistrations =  new PersonRegistrationsType();
    $personNewRegistrations = new PersonRegistrationsType();
    $request = new UpdateRegistrationsRequestType();

    // get person
    $person = $fifaConnectIdClient->getPersonApi()->getPersonData($myPersonFifaId);
    $playerOldRegistrations = $person->getPlayerRegistrations();
    $personOldRegistrations->setPlayerRegistrations($playerOldRegistrations);

    // collection of player registrations is now filled with the actual data;
    // for the simplicty reasons in this example we assume that the person has exactly 2 player registrations and that they will be listed in the correct order

    // fill up updated registrations
    $updateRegistration1->setDiscipline('Football')
        ->setLevel('Pro')
        ->setStatus('active')
        ->setOrganisationFifaId($organisationId);

    $updateRegistration2->setDiscipline('BeachSoccer')
        ->setLevel('Amateur')
        ->setStatus('active')
        ->setOrganisationFifaId($organisationId);

    $playerNewRegistrations[] = $updateRegistration1;
    $playerNewRegistrations[] = $updateRegistration2;
    $personNewRegistrations->setPlayerRegistrations($playerNewRegistrations);

    // prepare request
    $request->setPersonId($myPersonFifaId)
        ->setOldRegistrations($personOldRegistrations)
        ->setNewRegistrations($personNewRegistrations);

    try {
        $result = $fifaConnectIdClient->getPersonApi()->updateRegistrations($request, false);
    } catch (InvalidClientDataException $e) {
        // data sent to the service was invalid or the Registration System lacks permissions to perform the operation
        $badRequest = $e->getBadRequestResponse();
    } catch (AuthenticationException $e) {
        // invalid client credentials
    } catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    } catch (FifaConnectIdException $e) {
        // some other error occurred, see the details
    } catch (UnauthorizedException $e) {
        // unauthorized
    } catch (ValidationException $e) {
        // validation error occurred
    }

Please note that each time an operation succeed, you will receive an object of class `PersonType` containing the current status of a person with all their registrations. This feature may be used for troubleshooting but also to synchronize the local data with a central DB (ID Directory).

Also note that errors are not thrown if you perform an operation which would result in the object not changing its state. For example, if you want to add a registration for a Football Player, Amateur in France, and such a registration already exists, error will not be thrown and the operation will succeed.

#### 3.3.6.3 Remove registrations

We do not provide SDK method for removing registrations. Correct operation here would be updating registration by setting its status to inactive (deactivation). If you are certain that a registration should be removed (e.g. it has been added by mistake), please contact our Helpdesk.

### 3.3.7 Updating a person

Note, that when updating the name of a person, you need to specify all pairs of names. For example if you originally registered a person with both local and international name, you need to specify both pairs even if you are changing only one name. Connect ID is only able to replace an existing set of names with a new set. The reason for this is personal data protection, which allows the service to store hashes but not names.

To update a person associated with the specified FIFA ID:

    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\FifaEntityNotFoundException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\Model\PersonName;
    use Fifa\ConnectId\Sdk\Util\Data\Gender;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    $names = array(
        new PersonName('FirstName', 'LastName')
    );
    $personId = '105C40T';
    $dateOfBirth = new \DateTime('1990-01-31', new \DateTimeZone('UTC'));
    $gender = Gender::MALE;

    try {
        $updatedPerson = $fifaConnectIdClient->getPersonApi()->updatePerson($personId, $names, $dateOfBirth, $gender);
    }
    catch (InvalidClientDataException $e) {
        // data sent to the service was invalid or the Registration System lacks permission to perform the operation
        $badRequest = $e->getBadRequestResponse();
    }
    catch (FifaEntityNotFoundException $e) {
        // could not find the person
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // some other error occurred, see the details
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

### 3.3.8 Adding registrations handled by external system (SystemId)

By default, all registrations are stored in a single national registration system. It is however possible to connect additional system to FIFA Connect ID services. As an example we may consider the [main] national registration system being responsible for Amateur players only and an additional, external system – for Professionals. In such a setup it is important that Connect ID is able to properly route requests for player details sent by other Registration Systems via Service Bus.

In order for this routing to work correctly, an external system, for each of registrations it manages (professional ones), must provide its unique identifier (System ID).

System ID can be set by using `setSystemId()` method. See below example:

    use Fifa\ConnectId\Sdk\Api\Model\PlayerRegistrationType;
    use Fifa\ConnectId\Sdk\Util\Data\Discipline;
    use Fifa\ConnectId\Sdk\Util\Data\Person\Level;
    use Fifa\ConnectId\Sdk\Util\Data\RegistrationNature;
    use Fifa\ConnectId\Sdk\Util\Data\Status;

    $organisationId = '105C8LX';
    $systemId = '105C8LX_SystemName';
    $newPlayerRegistration = new PlayerRegistrationType();

    $newPlayerRegistration->setLevel(Level::PRO);
    $newPlayerRegistration->setDiscipline(Discipline::FOOTBALL);
    $newPlayerRegistration->setRegistrationNature(RegistrationNature::REGISTRATION);
    $newPlayerRegistration->setStatus(Status::ACTIVE);
    $newPlayerRegistration->setOrganisationFifaId($organisationId);
    $newPlayerRegistration->setSystemId($systemId); // The system identifier is created and passed along with the access credentials.

There is no need of setting System ID if registration is added by a default system. In this case the `<null>` value should be used.

### 3.3.9 Bulk deactivating registrations

It is possible to bulk deactivate a set of given registrations using `bulkDeactivateRegistrations()` method, which is available in `PersonApi` class. As an input method takes a list of person registrations that will be deactivated. Only listed registrations will be deactivated - all other registrations that exist in the context of a given person will be left unchanged. Each registration needs to represent the current state (including status), otherwise server won't recognize it and will return error message that registration doesn't exist. One deactivation request can have up to 500 registrations in total (player, match official, team official and organisation official). Below you can find an example of bulk deactivation usage:

    use Fifa\ConnectId\Sdk\Api\Model\BulkDeactivateRegistrationsRequestType;
    use Fifa\ConnectId\Sdk\Api\Model\PersonDeactivateType;
    use Fifa\ConnectId\Sdk\Api\Model\PersonRegistrationsType;
    use Fifa\ConnectId\Sdk\Api\Model\PlayerRegistrationType;
    use Fifa\ConnectId\Sdk\Api\Model\TeamOfficialRegistrationType;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\Exception\UnauthorizedException;
    use Fifa\ConnectId\Sdk\Model\PersonData;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    $personsToDeactivate = [];

    // create request
    foreach($persons as $person) {

        /** @var string $personId */
        $personId = $person['person_id'];
        /** @var PersonData $personData */
        $personData = $person['person_data'];

        // pick registrations to deactivate
        $playerRegistrations = $personData->getPersonType()->getPlayerRegistrations();
        /** @var PlayerRegistrationType[] $playerRegistrationsToDeactivate */
        $playerRegistrationsToDeactivate = [
            $playerRegistrations[0], $playerRegistrations[2]
        ];

        $teamOfficialRegistrations = $personData->getPersonType()->getTeamOfficialRegistrations();
        /** @var TeamOfficialRegistrationType[] $teamOfficialRegistrationsToDeactivate */
        $teamOfficialRegistrationsToDeactivate = [
            $teamOfficialRegistrations[3]
        ];

        $personsToDeactivate[] = new PersonDeactivateType([
            'person_id' => $personId,
            'registrations' => new PersonRegistrationsType([
                'player_registrations' => $playerRegistrationsToDeactivate,
                'team_official_registrations' => $teamOfficialRegistrationsToDeactivate,
                'match_official_registrations' => $personData->getPersonType()->getMatchOfficialRegistrations(), // deactivate all Match Official Registrations
            ])
        ]);
    }

    $request = new BulkDeactivateRegistrationsRequestType(['persons' => $personsToDeactivate]);

    try {
        $response = $fifaConnectIdClient->getPersonApi()->bulkDeactivateRegistrations($request);

        /** @var BulkDeactivateRegistrationsResult[] $failedRegistrations */
        $failedRegistrations = [];
        foreach ($response->getResults() as $result) {
            if (!$result->getSuccess()) {
                $failedRegistrations[] = $result;
            }
        }
    }
    catch (InvalidClientDataException $e) {
        // data sent to the service was invalid
        $badRequest = $e->getBadRequestResponse();
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // some other error occurred, see the details
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

`bulkDeactivateRegistrations()` returns `BulkDeactivateRegistrationsResponseType`. Then it is possible to check the HTTP status of the operation as well as get results of deactivation process (by calling `getResults()` method on the response object). This (`getResults()`) method returns an array of `BulkDeactivateRegistrationsResult`. Each instance of `BulkDeactivateRegistrationsResult` contains information about a status of specific person oriented operation. Possible methods are: `getPersonId()`, `getSuccess()`, `getException()`. In case deactivation fails for given person, `getSuccess()` will be set as `false` and additional information will be provided as an exception.

Please note that bulk deactivation is a heavy operation that affects server resources. Before running it please * make sure that you notified Support Team (support.id@fifa.org), so we could scale up the system * make sure that your PHP server timeouts as well as Curl timeouts (possible to set in a client class) allow to handle long lasting operations

### 3.3.10 Data holders

Every Registration System that has or used to have a certain person's registration is known to be their DATA HOLDER. If Connect ID Service is aware of all data holders of a given FIFA_ID, it is able to properly build electronic player's passport and send notifications about data changes.

However, in some scenarios newly integrated Registration System may not be automatically registered as a data holder of a person. This will happen e.g. if they try to register an inactive player, they find a duplicate (because that player is active elsewhere) and simply store the FIFA_ID of a duplicate in their Registration System. Connect ID Service will not be aware that Registration System just became a data holder of that person.

Therefore, an additional mechanism of data holding declaration had been introduced. Registration System can now claim to be a data holder of a given FIFA_ID and be included in the data holders list. To claim that your Registration System is person's data holder, `dataHolderApi->registerAsDataHolderOfPerson` method should be called.

    try {
        $fifaConnectIdClient->getDataHolderApi()->registerAsDataHolderOfPerson($personFifaId);
    } catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    } catch (FifaEntityNotFoundException $e) {
        // could not find the person
    } catch (FifaEntityDeletedException $e) {
        // person has been deleted
    } catch (FifaPersonMergedException $e) {
        // requested person has been merged
        $person = $e->getRequestedPerson();
        $primaryPersonId = $e->getPrimaryPersonId();
    } catch (AuthenticationException $e) {
        // invalid client credentials
    } catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    } catch (FifaConnectIdException $e) {
        // handle exception
    } catch (UnauthorizedException $e) {
        // unauthorized
    } catch (ValidationException $e) {
        // validation error occurred
    }

The method is idempotent i.e. can be called multiple times without any negative side-effects. It is important to note that the method only enqueues your registration request. The actual processing happens in the background. The implications are twofold: 1. The call will return as soon as the request is accepted. 2. It may take some time for the system to process the request

In order to confirm that your request has been processed, you can call another method `dataHolderApi->getDataHoldersOfPerson($personFifaId)` which returns the list of systems who are Data Holders of the given person FIFA_ID. After successful processing, the list should contain your Registration System's ID. However, it is not recommended to call it after every call to `registerAsDataHolderOfPerson`.

## 3.4 Sharing Person's details with any system over the Service Bus

You can share the details of any person with another system over the Service Bus. It is important for you to agree on the message type that will be used both by the sender and the recipient.

### 3.4.1 Sending PersonLocal object

You can send a PersonLocal object to any system using an instance of `PersonLocalSender`. Please note that the `messageType` argument has be to agreed with the receiving system so that they can register a handler for it. You can find more information on the subject in our portal.

    $personLocalSender = new PersonLocalSender($registrationFacade->getServiceBusClient());

    $recipient = '105C3ZB_CSRS';
    $messageType = 'expected-message-type';
    $personLocal = new PersonLocal(
        'ΧΑΡΑΛΑΜΠΟΥΣ', Gender::MALE, 'CY', new \DateTime('1989-05-31', new \DateTimeZone('UTC')),
        'CY', 'Larnaca', 'gre', 'CY', '1XDZR79'
    );

    try {
        $personLocalSender->send($personLocal, $recipient, $messageType);
    } catch (XmlSerializationException $e) {
        // handle serialization exception
    } catch (ValidationException $e) {
        // validation error occurred
    } catch (AuthenticationException $e) {
        // invalid client credentials
    } catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    } catch (QueueNotFoundException $e) {
        // invalid recipient
    } catch (UnauthorizedException $e) {
        // unauthorized
    } catch (FifaConnectServiceBusSdkException $e) {
        $details = $e->getResponseBody();
    }

### 3.4.2 Receiving PersonLocal object

In order to receive Person's details sent by another system over the Service Bus (according to section 3.4.1) you need to register an extra message handler for the agreed message type. Our SDK will take care of validation and deserialization of the message. All you need to provide is an implementation of your own handler that will be invoked when the PersonLocal object is received.

    use Fifa\ConnectId\Sdk\MessageHandlers\PersonLocalReceivedHandlerInterface;
    use Fifa\ConnectId\Sdk\Model\DataStandard\PersonLocal;

    class PersonLocalReceivedHandler implements PersonLocalReceivedHandlerInterface {
        public function handle(PersonLocal $personLocal, string $sender)
        {
            // TODO: Implement handle() method.
        }
    }

When configuring the Listener add an extra message handler:

    $personLocalReceivedHandler = new PersonLocalReceivedHandler();
    $messageType = 'expected-message-type';
    $personLocalMessageHandler = new PersonLocalMessageHandler($personLocalReceivedHandler, $messageType);

    // add the new message handler to the collection of existing ones
    $messageHandlers[] = $personLocalMessageHandler;

# 4. Organisation

All operations that refer to Organisations are performed using the `FifaConnectIdClient`.

## 4.1 Registering an organisation

To register a new organisation:

    use Fifa\ConnectId\Sdk\Api\Model\OrganisationLocalType;
    use Fifa\ConnectId\Sdk\Environment\Environment;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\FifaDuplicatedOrganisationFoundException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\FifaConnectIdClient;
    use Fifa\ConnectServiceBus\Sdk\Authentication\Model\ClientCredentials;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    $organisationType = new OrganisationLocalType();
    $organisationType->setStatus('active');
    // fill up all required data according to FIFA Data Standard

    try {
        $organisationFifaId = $fifaConnectIdClient->getOrganisationApi()->register($organisationType);
    }
    catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    }
    catch (FifaDuplicatedOrganisationFoundException $ex) {
        // possible duplicates were found
        $duplicates = $ex->getGetDuplicatesResponse()->getDuplicates();
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle exception
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

If duplicated organisation is found then `FifaDuplicatedOrganisationFoundException` will be thrown. In order to resolve conflict and register new organisation use optional `force` parameter as presented below:

    $force = true;
    $organisationFifaId = $fifaConnectIdClient->getOrganisationApi()->register($organisationType, $force);

## 4.2 Getting organisation data

To get the organisation data associated with the specified FIFA ID:

    use Fifa\ConnectId\Sdk\Environment\Environment;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\FifaOrganisationMergedException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\FifaConnectIdClient;
    use Fifa\ConnectServiceBus\Sdk\Authentication\Model\ClientCredentials;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    $organisationFifaId = '1000G88';

    try {
        $organisationType = $fifaConnectIdClient->getOrganisationApi()->getOrganisation($organisationFifaId);
    }
    catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    }
    catch (FifaOrganisationMergedException $e) {
        // requested organisation was merged and is inactive
        $organisation = $e->getRequestedOrganisation();
        $primaryOrganisationId = $e->getPrimaryOrganisationFifaId();
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle exception
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

This method will throw `FifaOrganisationMergedException` when you try to get data for an organisation that has been previously merged and deactivated (refer to section 4.3 Merging/unmerging organisations). The exception will contain data about requested organisation together with primary organisation id.

## 4.3 Merging/unmerging organisations

To merge two organisations into a single one use `mergeOrganisations` method available on `OrganisationApi`. This operation will only work for clubs. In addition you can only merge those that belong to the same Member Association that you are a member of. As a result of this operation, secondary organisation status will be set to `inactive`. This affects getting secondary organisation data in the future. Please refer to section 4.2 Getting organisation data. Here is an example how to merge organisations:

    use Fifa\ConnectId\Sdk\Environment\Environment;
    use Fifa\ConnectId\Sdk\Exception\FifaConflictException;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\FifaEntityNotFoundException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\FifaConnectIdClient;
    use Fifa\ConnectServiceBus\Sdk\Authentication\Model\ClientCredentials;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    try {
        $fifaConnectIdClient->getOrganisationApi()->mergeOrganisations($primaryOrganisationId, $secondaryOrganisationId);
    }
    catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    }
    catch (FifaConflictException $e) {
        // a conflicting operation is currently in progress
        $details = $e->getConflictResponseType();
    }
    catch (FifaEntityNotFoundException $e) {
        // could not find the organisation
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle exception
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

In order to revert merge operation call `unmergeOrganisations` method with the same arguments.

## 4.4 Updating an organisation

To update the organisation data use `update` method that is available in `OrganisationApi` class.

    use Fifa\ConnectId\Sdk\Api\Model\OrganisationLocalType;
    use Fifa\ConnectId\Sdk\Environment\Environment;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\FifaDuplicatedOrganisationFoundException;
    use Fifa\ConnectId\Sdk\Exception\FifaEntityNotFoundException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\FifaConnectIdClient;
    use Fifa\ConnectId\Sdk\Util\Data\Status;
    use Fifa\ConnectServiceBus\Sdk\Authentication\Model\ClientCredentials;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    $organisationFifaId = '1000G88';

    try {
        /** @var OrganisationLocalType $organisationType */
        $organisationType = $fifaConnectIdClient->getOrganisationApi()->getOrganisation($organisationFifaId);

        // set the data to update
        $organisationType->setStatus(Status::INACTIVE);
        $organisationType->setEmail('some@other.email');

        $updatedOrganisaion = $fifaConnectIdClient->getOrganisationApi()->update($organisationType);
    }
    catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    }
    catch (FifaEntityNotFoundException $e) {
        // could not find the organisation
    }
    catch (FifaDuplicatedOrganisationFoundException $ex) {
        // possible duplicates were found
        $duplicates = $ex->getGetDuplicatesResponse()->getDuplicates();
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle exception
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

If duplicated organisation is found then `FifaDuplicatedOrganisationFoundException` will be thrown. In order to resolve conflict and update organisation use optional `force` parameter.

## 4.5 Searching for an organisation

`findOrganisations` method takes `FindOrganisationsRequestType` as a parameter that aggregates search criteria. It's possible to search by international and local names, parent organisation, organisation nature, address and last modification date.

    use Fifa\ConnectId\Sdk\Api\Model\SearchAddressType;
    use Fifa\ConnectId\Sdk\Api\Model\FindOrganisationsRequestType;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\Exception\UnauthorizedException;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    $address = new SearchAddressType();
    $address->setCountry('ES')
        ->setTown('Barcelona');

    $request = new FindOrganisationsRequestType();
    $request->setInternationalName('Barcelona')
        ->setOfficialAddress($address)
        ->setModifiedFromDateTime(new \DateTime('-5 days', new \DateTimeZone('UTC')))
        ->setModifiedToDateTime(new \DateTime('now', new \DateTimeZone('UTC')))
        ->setNumberOfResults(5);

    try {
        $response = $fifaConnectIdClient->getOrganisationApi()->findOrganisations($request);
        $organisations = $response->getOrganisations();
    } catch (UnauthorizedException $e) {
        // unauthorized
    } catch (AuthenticationException $e) {
        // invalid client credentials
    } catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    } catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    } catch (FifaConnectIdException $e) {
        // handle exception
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

## 4.6 Get organisation duplicates

To get a list of duplicates for an organisation use `getDuplicates()` method. This method looks for similar organisations already registered in FIFA Connect ID Service and returns an array of potential duplicates.

    use Fifa\ConnectId\Sdk\Api\Model\OrganisationLocalType;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\Exception\UnauthorizedException;
    use Fifa\ConnectId\Sdk\Util\Data\Organisation\OrganisationNature;
    use Fifa\ConnectId\Sdk\Util\Data\Status;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    $organisation = new OrganisationLocalType();
    $organisation->setStatus(Status::ACTIVE)
        ->setInternationalName('Organisation name')
        ->setOrganisationNature(OrganisationNature::CLUB);
    // fill up all required data according to FIFA Data Standard

    try {
        $foundDuplicates = $fifaConnectIdClient->getOrganisationApi()->getDuplicates($organisation);
    }
    catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle generic exception
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

## 4.7 Get National Associations onboarded status

To get a list with onboarded statuses (has any persons or suborganisations registered) for specific National Associations use `getNationalAssociationsOnboardedStatus()` method. It takes `NationalAssociationsOnboardedRequestType` as a parameter which should contain list of strings with National Associations FIFA IDs.

    use Fifa\ConnectId\Sdk\Api\Model\NationalAssociationsOnboardedRequestType;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\FifaEntityNotFoundException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;
    use Fifa\ConnectServiceBus\Sdk\Exception\UnauthorizedException;

    $request = new NationalAssociationsOnboardedRequestType();
    $request->setNationalAssociationsFifaIds(['BVGE8TG', 'AHAR8SV']);

    try {
        $response = $fifaConnectIdClient->getOrganisationApi()->getNationalAssociationsOnboardedStatus($request);
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    }
    catch (FifaEntityNotFoundException $e) {
        // could not find the organisation
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle generic exception
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

To get a list with onboarded statuses for all National Associations use `getAllNationalAssociationsOnboardedStatus()` method.

    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;
    use Fifa\ConnectServiceBus\Sdk\Exception\UnauthorizedException;

    try {
        $response = $fifaConnectIdClient->getOrganisationApi()->getAllNationalAssociationsOnboardedStatus();
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle generic exception
    }

## 4.8 Get Member Association of a child organisation (e.g. a club)

Some Member Associations may have a multi-level structure of children organisations, e.g. teams, clubs, schools, regions, etc. In many cases, however, there is a need to find out to which Member Association a given organisation (e.g. a club) belongs to. To get FIFA ID and short name of that Member Association use `getMemberAssociationForFifaId` method with the organisation's FIFA ID as input.

Note, that in order to retrieve the full name of the Member Association, call `getOrganisation` passing FIFA ID of the Member Association.

    $uniqueFifaId = 'BVGE8TG';

    try {
        $memberAssociation = $fifaConnectIdClient->getOrganisationApi()->getMemberAssociationForFifaId($uniqueFifaId);
    } catch (FifaEntityNotFoundException $e) {
        // could not find the memberAssociation
    } catch (UnauthorizedException $e) {
        // unauthorized
    } catch (InvalidClientDataException $e) {
        // provided organisation id is not valid
        $details = $e->getBadRequestResponse();
    } catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    } catch (FifaConnectIdException $e) {
        // handle exception
    } catch (AuthenticationException $e) {
        // invalid client credentials
    } catch (ValidationException $e) {
        // validation error occurred
    }

# 5. Facility

All operations that refer to Facilities are performed using the `FifaConnectIdClient`.

## 5.1 Registering a facility

To register a new facility run `registerFacility()` method that is available in instance of `FacilityApi`:

    use Fifa\ConnectId\Sdk\Api\Model\FacilityLocalType;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\FifaDuplicatedFacilityFoundException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\Util\Data\Status;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    $facility = new FacilityLocalType();
    $facility->setStatus(Status::ACTIVE);
    $facility->setInternationalName('Wembley Stadium');
    $facility->setOrganisationFifaId('BVGE8TG');
    // fill up all required data according to FIFA Data Standard

    try {
        $facilityFifaId = $fifaConnectIdClient->getFacilityApi()->registerFacility($facility);
    }
    catch(InvalidClientDataException $e) {
        // data sent to the service was invalid
        $badRequest = $e->getBadRequestResponse();
    }
    catch (FifaDuplicatedFacilityFoundException $ex) {
        // possible duplicates were found
        $duplicates = $ex->getGetDuplicatesResponse()->getDuplicates();
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch(FifaConnectIdException $e) {
        // some other error occurred, see the details
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

If duplicated facility is found then `FifaDuplicatedFacilityFoundException` will be thrown. In order to resolve conflict and register new facility use optional `force` parameter.

## 5.2 Getting facility data

To get the facility data associated with the specified FIFA ID:

    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\FifaEntityNotFoundException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    $facilityId = '105C40T';

    try {
        $facility = $fifaConnectIdClient->getFacilityApi()->getFacility($facilityId);
    }
    catch (InvalidClientDataException $e) {
        // data sent to the service was invalid
        $badRequest = $e->getBadRequestResponse();
    }
    catch (FifaEntityNotFoundException $e) {
        // could not find the facility
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // some other error occurred, see the details
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

## 5.3 Updating a facility

To update the facility data use `update` method that is available in `FacilityApi` class.

    use Fifa\ConnectId\Sdk\Environment\Environment;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\FifaDuplicatedFacilityFoundException;
    use Fifa\ConnectId\Sdk\Exception\FifaEntityNotFoundException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\FifaConnectIdClient;
    use Fifa\ConnectId\Sdk\Util\Data\Status;
    use Fifa\ConnectServiceBus\Sdk\Authentication\Model\ClientCredentials;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    $uniqueFifaId = '105C40T';

    try {
        $facility = $fifaConnectIdClient->getFacilityApi()->getFacility($uniqueFifaId);

        // set the data to update
        $facility->setEmail('some@other.email');
        $facility->setStatus(Status::INACTIVE);

        $updatedFacility = $fifaConnectIdClient->getFacilityApi()->update($facility);
    }
    catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    }
    catch (FifaEntityNotFoundException $e) {
        // could not find the facility
    }
    catch (FifaDuplicatedFacilityFoundException $ex) {
        // possible duplicates were found
        $duplicates = $ex->getGetDuplicatesResponse()->getDuplicates();
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle exception
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

If duplicated facility is found then `FifaDuplicatedFacilityFoundException` will be thrown. In order to resolve conflict and update facility use optional `force` parameter.

## 5.4 Searching for a facility

To search for a facility use `findFacilities` method. It takes `FindFacilitiesRequestType` as a parameter that aggregates search criteria. It's possible to search by international and local names, parent facility, organisation, and address.

    use Fifa\ConnectId\Sdk\Api\Model\SearchAddressType;
    use Fifa\ConnectId\Sdk\Api\Model\FindFacilitiesRequestType;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\Exception\UnauthorizedException;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;

    $address = new SearchAddressType();
    $address->setCountry('ES')
        ->setTown('Barcelona');

    $request = new FindFacilitiesRequestType();
    $request->setInternationalName('Camp Nou')
        ->setModifiedFromDateTime(new \DateTime('-5 days', new \DateTimeZone('UTC')))
        ->setModifiedToDateTime(new \DateTime('now', new \DateTimeZone('UTC')))
        ->setOfficialAddress($address)
        ->setNumberOfResults(5);

    try {
        $response = $fifaConnectIdClient->getFacilityApi()->findFacilities($request);
        $foundFacilities = $response->getFacilities();
    } catch (UnauthorizedException $e) {
        // unauthorized
    } catch (AuthenticationException $e) {
        // invalid client credentials
    } catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    } catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    } catch (FifaConnectIdException $e) {
        // handle generic exception
    } catch (ValidationException $e) {
        // validation error occurred
    }

## 5.5 Get facility duplicates

To get a list of duplicates for a facility use `getDuplicates()` method. This method looks for similar facilities already registered in FIFA Connect ID service and returns an array of potential duplicates.

    use Fifa\ConnectId\Sdk\Api\Model\FacilityLocalType;
    use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
    use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
    use Fifa\ConnectId\Sdk\Exception\UnauthorizedException;
    use Fifa\ConnectId\Sdk\Util\Data\Status;
    use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;

    $facility = new FacilityLocalType();
    $facility->setStatus(Status::ACTIVE)
        ->setInternationalName('Facility name');
    // fill up all required data according to FIFA Data Standard

    try {
        $duplicates = $fifaConnectIdClient->getFacilityApi()->getDuplicates($facility);
    }
    catch (InvalidClientDataException $e) {
        $details = $e->getBadRequestResponse();
    }
    catch (TooManyRequestsException $e) {
        // call rate limit was exceeded
        $retryAfterInSeconds = $e->getRetryAfter();
    }
    catch (FifaConnectIdException $e) {
        // handle generic exception
    }
    catch (AuthenticationException $e) {
        // invalid client credentials
    }
    catch (UnauthorizedException $e) {
        // unauthorized
    }
    catch (ValidationException $e) {
        // validation error occurred
    }

# 6. [Optional] Transfer Matching System (TMS) / Clearing House integration

Transfer Matching System (TMS) is an online system facilitating international transfers of players. The FIFA Clearing House (FCH) is an entity acting as an intermediary for the payment of Training Rewards, i.e. Solidarity Contribution and Training Compensation.

Any Registration System who wants to satisfy the FCH requirements, needs to implement support for "FirstProRegistration", "ProofOfPayment" and "DomesticTransferDeclaration" messages. These messages are sent to TMS who further interacts with the FCH.

Please note, that this chapter focuses on the technical implementation only and does not give details about business scenarios that different methods should be used in. For the latter, check an article on our support portal.

## 6.1. Using TMS facade

### 6.1.1 TMS responses cache

When running the listener in a separate process, you need to provide a cache for incoming confirmation messages. Cache serves as shared storage for TMS responses received via Connect Service Bus. `TmsFacade` then utilizes it to check the executing operation status.

Instead of using the provided SDK's `InMemoryCorrelationIdBasedCache`, it is recommended that users establish their own storage solution for storing and retrieving incoming TMS responses.

Below are the interfaces that need to be implemented:

- `CorrelationIdBasedItemProviderInterface` - Used by the TMS facade to get records from cache by id. If there is no entry associated with the given id, `null` value should be returned
- `CorrelationIdBasedItemContainerInterface` - Used by the TMS message handlers factory to put records into the cache

The simplest and most recommended method is to create one cache class that manages all types of TMS responses:

    use Fifa\ConnectId\Sdk\Registration\CorrelationIdBasedItemContainerInterface;
    use Fifa\ConnectId\Sdk\Registration\CorrelationIdBasedItemProviderInterface;
    use Fifa\ConnectId\Sdk\Registration\MessageReceived;

    class TmsResponsesCache implements CorrelationIdBasedItemContainerInterface, CorrelationIdBasedItemProviderInterface
    {
        /**
         * Method stores serialized data in cache system.
         */
        public function put($correlationId, MessageReceived $item)
        {
            throw new Exception('Not implemented');
        }

        /**
         * Method returns serialized data from the cache system.
         */
        public function getByCorrelationId($correlationId)
        {
            throw new Exception('Not implemented');
        }
    }

### 6.1.2 TMS message handlers

The TMS Facade relies on the registration facade to listen for incoming responses from TMS. It utilizes the same listener that receives personal details during person registration.

Furthermore, it is essential to pass the same instance of the responses cache class into the TMS message handlers as TMS facade. Otherwise, the facade will never be able to confirm the status of TMS operation.

In order to hook TMS facade into the existing listener infrastructure, you add new message handlers to the current listener:

    $tmsMessageHandlers = TmsFacadeMessageHandlersFactory::createMessageHandlers($tmsResponsesCache);

    array_push($messageHandlers, ...$tmsMessageHandlers);

### 6.1.3 Create TMS facade

In order to integrate with TMS, you need to instantiate a dedicated facade object that will serve as an entry point for all TMS-related operations.

    $responseAwaitersFactory = new CachePollingResponseAwaitersFactory();

    $tmsFacade = TmsFacade::create(
        $registrationFacade->getServiceBusClient(), // 2.3 Using Registration Facade
        $logger,                                    // 2.3.4 Logger
        $responseAwaitersFactory,
        $tmsResponsesCache);                        // 6.1.1 TMS responses cache

## 6.2 Add person to TMS

In order to add a person to TMS, you need to build a `PersonLocal` object and pass it to `addPlayer` method. `PersonLocal` type is compliant with FIFA Data Standard. Please mind that property `PersonFIFAId` is obligatory.

    use Fifa\ConnectId\Sdk\Exception\TmsOperationException;
    use Fifa\ConnectId\Sdk\Model\DataStandard\PersonLocal;
    use Fifa\ConnectId\Sdk\Util\Data\Gender;

    $personLocal = new PersonLocal(
        'ΧΑΡΑΛΑΜΠΟΥΣ', Gender::MALE, 'CY', new \DateTime('1989-05-31', new \DateTimeZone('UTC')),
        'CY', 'Larnaca', 'gre', 'CY', '1XDZR79'
    );

    try {
        $correlationId = $tmsFacade->addPlayer($personLocal);
    } catch (TmsOperationException $e) {
        // issue occurred during client side TMS operation
        $details = $e->getPrevious();
    }

Notice that in this scenario you don't wait for TMS to answer. `correlationId` does not prove that the message has been picked up by TMS, only that it has been successfully sent. Although `correlationId` is not required for any further processing, we recommend that you log it.

## 6.3 Update person in TMS

In order to update a person in TMS, you need to build a `PersonLocal` object and pass it to `updatePlayer` method. `PersonLocal` type is compliant with FIFA Data Standard. Please mind that property `PersonFIFAId` is obligatory.

    use Fifa\ConnectId\Sdk\Exception\TmsOperationException;
    use Fifa\ConnectId\Sdk\Model\DataStandard\PersonLocal;
    use Fifa\ConnectId\Sdk\Util\Data\Gender;

    $personLocal = new PersonLocal(
        'ΧΑΡΑΛΑΜΠΟΥΣ', Gender::MALE, 'CY', new \DateTime('1989-05-31', new \DateTimeZone('UTC')),
        'CY', 'Larnaca', 'gre', 'CY', '1XDZR79'
    );

    try {
        $correlationId = $tmsFacade->updatePlayer($personLocal);
    } catch (TmsOperationException $e) {
        // issue occurred during client side TMS operation
        $details = $e->getPrevious();
    }

Notice that in this scenario you don't wait for TMS to answer. `correlationId` does not prove that the message has been picked up by TMS, only that it has been successfully sent. Although `correlationId` is not required for any further processing, we recommend that you log it.

## 6.4 Send Domestic Transfer Declaration

TMS facade sends the declaration to the Service Bus. TMS is expected to read the declaration and send a Domestic Transfer Confirmation back with a TMS Transfer ID.

Once the confirmation is received you have to store the `tmsTransferId`. It will be required for sending Proof Of Payment message later on.

    use Fifa\ConnectId\Sdk\Model\DataStandard\PersonLocal;
    use Fifa\ConnectId\Sdk\Model\DataStandard\PlayerRegistration;
    use Fifa\ConnectId\Sdk\Model\Transfer\Club;
    use Fifa\ConnectId\Sdk\Model\Transfer\ConditionalTransferFee;
    use Fifa\ConnectId\Sdk\Model\Transfer\DomesticTransferDeclaration;
    use Fifa\ConnectId\Sdk\Model\Transfer\TransferFeeDetails;
    use Fifa\ConnectId\Sdk\Util\Data\Transfer\SellOnFee;
    use Fifa\ConnectId\Sdk\Util\Data\Transfer\TransferNature;
    use Fifa\ConnectId\Sdk\Util\Data\Person\Level;
    use Fifa\ConnectId\Sdk\Util\Data\Gender;
    use Fifa\ConnectId\Sdk\Util\Data\Status;
    use Fifa\ConnectId\Sdk\Util\Data\Discipline;
    use Fifa\ConnectId\Sdk\Util\Data\RegistrationNature;
    use Fifa\ConnectId\Sdk\Exception\TmsOperationException;
    use Fifa\ConnectId\Sdk\Tms\DeclarationRejectedException;
    use Fifa\ConnectId\Sdk\Tms\ResponseNotReceivedException;
    use InvalidArgumentException;
    use Exception;

    // build PersonLocal
    $personFIFAId = '1XDZR79';
    $personLocal = new PersonLocal(
        'ΧΑΡΑΛΑΜΠΟΥΣ', Gender::MALE, 'CY', new \DateTime('1989-05-31', new \DateTimeZone('UTC')),
        'CY', 'Larnaca', 'gre', 'CY', $personFIFAId
    );

    // add PlayerRegistration
    $status = Status::ACTIVE;
    $organisationFIFAId = '103C3AB';
    $registrationValidFrom = new \DateTime('2020-06-01', new \DateTimeZone('UTC'));
    $registrationLevel = strtolower(Level::PRO);
    $discipline = Discipline::FOOTBALL;
    $registrationNature = RegistrationNature::REGISTRATION;

    $transferRegistration = new PlayerRegistration($personFIFAId, $status, $organisationFIFAId, $registrationValidFrom, $registrationLevel, $discipline, $registrationNature);
    $personLocal->addPlayerRegistration($transferRegistration);

    // build DomesticTransferDeclaration
    $messageId = 'information uniquely identifying this particular declaration';
    $sellOnFee = SellOnFee::NO; // if no information is available, set to SellOnFee::NOT_AVAILABLE
    $engagingClub = new Club('ARZI7RL', 'Q8ZZ8RT', 'Engaging Club International Name');
    $releasingClub = new Club('HNIUE01', 'SW8FJ2P', 'Releasing Club International Name');
    $transferNature = TransferNature::PERMANENT;
    $contractEndDate = new \DateTime('2030-01-01', new \DateTimeZone('UTC'));

    $levelInReleasingClub = strtolower(Level::PRO);
    $levelInEngagingClub = strtolower(Level::PRO);
    $declaration = new DomesticTransferDeclaration(
        $messageId, $personLocal, $engagingClub, $releasingClub,
        $levelInEngagingClub, $levelInReleasingClub, $transferNature, $contractEndDate, $sellOnFee
    );

    //add conditional transfers fees
    $dateOfPayment = new \DateTime('2020-06-01', new \DateTimeZone('UTC'));
    $recipientClub = new Club('HNIUE01', 'SW8FJ2P', 'Transfer Fee Club International Name');
    $conditionalTransferFeeDetails = new TransferFeeDetails(100,"EUR", $dateOfPayment, $recipientClub );
    $conditionalTransferFee = new ConditionalTransferFee($conditionalTransferFeeDetails);
    $conditionalTransferFee -> Condition = "Specify condition";

    $declaration->ConditionalTransferFee = array($conditionalTransferFee);

    //add fixed transfer fees

    //add release buyout transfer fees

    //populate the declaration with data

    $timeoutInSeconds = 30;

    try {
        $tmsTransferId = $tmsFacade->declareDomesticTransferAndWaitForTransferId($declaration, $timeoutInSeconds);
    } catch (TmsOperationException $tmsOperationException) {
        // Errors when trying to send message to TMS. Check inner exception for details
        $details = $tmsOperationException->getMessage();
        $innerException= $tmsOperationException->getPrevious()->getMessage();
    } catch (DeclarationRejectedException $declarationRejectedException) {
        // TMS rejected the declaration. See the reason for details:
        $rejectionReason = $declarationRejectedException->getReason();
    } catch (ResponseNotReceivedException $transferIdNotReceivedException) {
        // Possible reasons:
        // 1. TMS has not responded within the timeout (consider increasing the timeout)
        // 2. messagesStorage is not populated by the listener
    } catch (InvalidArgumentException $invalidArgumentException) {
        // Data found in the messages storage was not of type `DomesticTransferConfirmation`
    } catch (Exception $exception) {
        // Something else went wrong
    }

The facade waits for a given period of time for the response. Should the response not arrive, ResponseNotReceivedException exception is thrown.

Should TMS consider the request invalid, `DeclarationRejectedException` will be thrown with a message from TMS providing the reason of the rejection.

## 6.5 Send Proof of Payment

In order to send a Proof of Payment to TMS, you need to build a `ProofOfPayment` object and pass it to `provePaymentAndWaitForPaymentId` method. Please mind that all properties are obligatory.

TMS facade sends the proof to the Service Bus. TMS is expected to read the proof and send a Proof Of Payment Confirmation back with a TMS Payment ID.

As of May 2024 there is no requirement to store `tmsPaymentId`. We recommend you log it as a confirmation that the message has been sent & received successfully.

    use Fifa\ConnectId\Sdk\Model\Transfer\ProofOfPayment;
    use Fifa\ConnectId\Sdk\Util\Data\Transfer\PaymentType;
    use Fifa\ConnectId\Sdk\Model\Transfer\Club;
    use Fifa\ConnectId\Sdk\Model\Transfer\PaymentDetails;
    use Fifa\ConnectId\Sdk\Exception\TmsOperationException;
    use Fifa\ConnectId\Sdk\Tms\DeclarationRejectedException;
    use Fifa\ConnectId\Sdk\Tms\ResponseNotReceivedException;
    use InvalidArgumentException;
    use Exception;

    $messageId = 'information uniquely identifying this particular payment, e.g. bank transfer id';
    $tmsTransferId = 1234;
    $paymentType = PaymentType::FIXED_TRANSFER_FEE;
    $dateOfPayment = new \DateTime('2020-06-01', new \DateTimeZone('UTC'));
    $recipientClub = new Club('HNIUE01', 'SW8FJ2P', 'Recipient Club International Name');
    $paymentDetails = new PaymentDetails(10000000, 'EUR', $dateOfPayment, $recipientClub);

    $proof = new ProofOfPayment($messageId, $tmsTransferId, $paymentType, $paymentDetails, $recipientClub);

    try {
        $paymentId = $tmsFacade->provePaymentAndWaitForPaymentId($proof, $timeoutInSeconds);
    } catch (DeclarationRejectedException $declarationRejectedException) {
        // TMS rejected the declaration. See the reason for details:
        $rejectionReason = $declarationRejectedException->getReason();
    } catch (TmsOperationException $tmsOperationException) {
        // Errors when trying to send message to TMS. Check inner exception for details
        $details = $tmsOperationException->getMessage();
        $innerException= $tmsOperationException->getPrevious()->getMessage();
    } catch (ResponseNotReceivedException $transferIdNotReceivedException) {
        // Possible reasons:
        // 1. TMS has not responded within the timeout (consider increasing the timeout)
        // 2. messagesStorage is not populated by the listener
    } catch (InvalidArgumentException $invalidArgumentException) {
        // Data found in the messages storage was not of type `ProofOfPaymentConfirmation`
    } catch (Exception $exception) {
        // Something else went wrong
    }

The facade waits for a given period of time for the response. Should the response not arrive, `ResponseNotReceivedException` exception is thrown.

Should TMS consider the request invalid, `DeclarationRejectedException` will be thrown with a message from TMS providing the reason of the rejection.

## 6.6 Send First Pro Registration

TMS facade sends the registration to the Service Bus. TMS is expected to read the registration and send a First Pro Registration Confirmation back with a TMS First Registration ID.

As of May 2024 there is no requirement to store `tmsFirstRegistrationId`. We recommend you log it as a confirmation that the message has been sent & received successfully.

    use Fifa\ConnectId\Sdk\Model\DataStandard\PersonLocal;
    use Fifa\ConnectId\Sdk\Util\Data\Gender;
    use Fifa\ConnectId\Sdk\Model\DataStandard\PlayerRegistration;
    use Fifa\ConnectId\Sdk\Util\Data\Status;
    use Fifa\ConnectId\Sdk\Util\Data\Discipline;
    use Fifa\ConnectId\Sdk\Util\Data\RegistrationNature;
    use Fifa\ConnectId\Sdk\Model\Transfer\FirstProRegistration;
    use Fifa\ConnectId\Sdk\Model\Transfer\Club;
    use Fifa\ConnectId\Sdk\Util\Data\Transfer\ClubStatus;
    use Fifa\ConnectId\Sdk\Util\Data\Person\Level;
    use Fifa\ConnectId\Sdk\Tms\ResponseNotReceivedException;
    use Fifa\ConnectId\Sdk\Tms\DeclarationRejectedException;
    use InvalidArgumentException;
    use Exception;

    // build PersonLocal
    $personFIFAId = '1XDZR79';
    $player = new PersonLocal(
        'ΧΑΡΑΛΑΜΠΟΥΣ', Gender::MALE, 'CY', new \DateTime('1989-05-31', new \DateTimeZone('UTC')),
        'CY', 'Larnaca', 'gre', 'CY', $personFIFAId
    );

    // add PlayerRegistration
    $status = Status::ACTIVE;
    $organisationFIFAId = '103C3AB';
    $registrationValidFrom = new \DateTime('2020-06-01', new \DateTimeZone('UTC'));
    $level = strtolower(Level::PRO);
    $discipline = Discipline::FOOTBALL;
    $registrationNature = RegistrationNature::REGISTRATION;

    $proRegistration = new PlayerRegistration($personFIFAId, $status, $organisationFIFAId, $registrationValidFrom, $level, $discipline, $registrationNature);
    $player->addPlayerRegistration($proRegistration);

    $club = new Club('HNIUE01', 'SW8FJ2P', 'Club International Name');
    $clubStatus = ClubStatus::ACTIVE;
    $playedTrainedInOtherMemberAssociation = true;
    $messageId = 'information uniquely identifying this particular registration';

    $registration = new FirstProRegistration($messageId, $player, $club, $clubStatus, $playedTrainedInOtherMemberAssociation);
    $timeoutInSeconds = 30;

    try {
        $tmsFirstRegistrationId = $tmsFacade->declareFirstProRegistrationAndWaitForRegistrationId($registration, $timeoutInSeconds);
    } catch (TmsOperationException $tmsOperationException) {
        // Errors when trying to send message to TMS. Check inner exception for details
        $details = $tmsOperationException->getMessage();
        $innerException= $tmsOperationException->getPrevious()->getMessage();
    } catch (DeclarationRejectedException $declarationRejectedException) {
        // TMS rejected the declaration. See the reason for details:
        $rejectionReason = $declarationRejectedException->getReason();
    } catch (ResponseNotReceivedException $responseNotReceivedException) {
        // Possible reasons:
        // 1. TMS has not responded within the timeout (consider increasing the timeout)
        // 2. messagesStorage is not populated by the listener
    } catch (InvalidArgumentException $invalidArgumentException) {
        // Data found in the messages storage was not of type `FirstProRegistrationConfirmation`
    } catch (Exception $exception) {
        // Something else went wrong
    }

The facade waits for a given period of time for the response. Should the response not arrive, `ResponseNotReceivedException` exception is thrown.

Should TMS consider the request invalid, `DeclarationRejectedException` will be thrown with a message from TMS providing the reason of the rejection.

# 7. [Optional] Automatic notifications

Certain business operations in FIFA Connect ID trigger notifications. For each notification type a subscription can be created, so that when a notification is triggered, then a message is sent out.

Two message types are supported: email and service bus. For the complete business documentation, list of notification types and methods to subscribe, please refer to Connect ID Notifications article.

In order to subscribe for a particular notification type, please get in touch with support.

Service bus messages are particularly interesting in the context of the SDK as it enables receiving them in an automated way. Under the hood, a service bus notification message is an XML document exchanged via the service bus infrastructure. The SDK handles the XML deserialization & validation and exposes an extension point you can hook into in order to process the message.

In order to use notification messages in your system, you need to implement an interface defined by the SDK and configure the listener to handle the messages.

## 7.1 Configuring listener

In order to take advantage of notifications, additional message handlers must be added when configuring the listener. Custom business logic can be invoked by implementing a notification handler interface and passing it as a constructor parameter to the corresponding message handler.

A stub implementation of a custom notification message handler:

    class CustomPersonMergedNotificationHandler implements PersonMergedNotificationHandlerInterface
    {
        /**
         * @inheritDoc
         */
        public function execute(PersonMergedNotification $personMerged)
        {
            // TODO: Implement execute() method.
        }
    }

To start handling the incoming notifications you need to add message handlers into current listener:

        array_push(
            $messageHandlers,
            new PersonMergedMessageHandler(new CustomPersonMergedNotificationHandler()),
            new PersonNameChangedMessageHandler(new CustomPersonNameChangedNotificationHandler()),
            new DateOfBirthChangedMessageHandler(new CustomDateOfBirthChangedNotificationHandler()),
            new GenderChangedMessageHandler(new CustomGenderChangedNotificationHandler()));

## 7.2 PersonMergedNotification

This notification is created when a person ("secondary") is merged into another person ("primary"). The secondary person FIFA ID is no longer active. With this notification message you can automate the process of updating your data. If you store any data for the secondary person, after the merge, you should replace the secondary person FIFA ID with the primary person FIFA ID in your Registration System.

### 7.2.1 Contents

The message contains the following information:

- Primary Person FIFA ID - Primary FIFA ID from the respective merge operation
- Secondary Person FIFA ID - Secondary FIFA ID from the respective merge operation
- Actor Name - Who performed the merge operation
- Operation Timestamp - When the merge operation was performed

### 7.2.2 Handler interface

SDK exposes the `PersonMergedNotificationHandlerInterface` interface which can be used to process a notification, e.g. update the FIFA ID in an internal Registration System's database. A stub implementation:

## 7.3 PersonNameChangedNotification

This notification is created when a change of a person name is made using update. If you store any data for the person, after the change, you might want to update a record in your DB.

Due to the fact that ConnectId does not store persons' names, there is no information on what was the name before and after a change. To get new data of the modified record you can contact the Registration System that triggered the notification.

### 7.3.1 Contents

The message contains the following information:

- PersonFIFAId - Id of a person that is a subject to the change
- Actor Name - Who performed the name change
- Operation Timestamp - When the name change was performed

### 7.3.2 Handler interface

SDK exposes the `PersonNameChangedNotificationHandlerInterface` interface which can be used to process a notification.

## 7.4 GenderChangedNotification

This notification is created when a change of a person gender is made using update. If you store any data for the person, after the change, you might want to update a record in your DB.

### 7.4.1 Contents

The message contains the following information:

- PersonFIFAId - Id of a person that is a subject to the change
- Before - Gender before the change
- After - Gender after the change
- Actor Name - Who performed the gender change
- Operation Timestamp - When the gender change was performed

### 7.4.2 Handler interface

SDK exposes the `GenderChangedNotificationHandlerInterface` interface which can be used to process a notification, e.g. update the person data in an internal Registration System's database.

## 7.5 DateOfBirthChangedNotification

This notification is created when a change of a person date of birth is made using update. If you store any data for the person, after the change, you might want to update a record in your DB.

### 7.5.1 Contents

The message contains the following information:

- PersonFIFAId - Id of a person that is a subject to the change
- Before - Date of birth before the change
- After - Date of birth after the change
- Actor Name - Who performed the date of birth change
- Operation Timestamp - When the date of birth change was performed

### 7.5.2 Handler interface

SDK exposes the `DateOfBirthChangedNotificationHandlerInterface` interface which can be used to process a notification, e.g. update the person data in an internal Registration System's database.

# 8. Utility tools

## 8.1 Person builder and predefined constants

An example:

    use Fifa\ConnectId\Sdk\Model\PersonData;
    use Fifa\ConnectId\Sdk\Util\Builder\Person\PersonBuilder;
    use Fifa\ConnectId\Sdk\Util\Data\Discipline;
    use Fifa\ConnectId\Sdk\Util\Data\Person\Level;
    use Fifa\ConnectId\Sdk\Util\Data\RegistrationNature;
    use Fifa\ConnectId\Sdk\Util\Data\Status;

    $personBuilder = PersonBuilder::create();
    $personBuilder
        ->addName('Smith', 'John')
        ->setDateOfBirth(new \DateTime('1990-04-26', new \DateTimeZone('UTC')))
        ->addPlayerRegistration(Level::AMATEUR, Discipline::FOOTBALL, Status::INACTIVE, '1000G88', RegistrationNature::LOAN)
        ->addPlayerRegistration(Level::PRO, Discipline::BEACH_SOCCER, Status::ACTIVE, '1000G88', RegistrationNature::LOAN)
        // ....
    ;

    /** @var PersonData $personData */
    $personData = $personBuilder->getPersonData();

You can find more predefined constants in classes located in `Fifa\ConnectId\Sdk\Util\Data` namespace;

## 8.2 Organisation builder

Similar to `PersonBuilder`, there is also `OrganisationBuilder`. An example:

    use Fifa\ConnectId\Sdk\Api\Model\OrganisationLocalType;
    use Fifa\ConnectId\Sdk\Util\Builder\Organisation\OrganisationBuilder;
    use Fifa\ConnectId\Sdk\Util\Data\Discipline;
    use Fifa\ConnectId\Sdk\Util\Data\Gender;
    use Fifa\ConnectId\Sdk\Util\Data\Organisation\OrganisationNature;
    use Fifa\ConnectId\Sdk\Util\Data\Status;

    $organisationBuilder = OrganisationBuilder::create();
    $organisationBuilder
        ->setStatus(Status::ACTIVE)
        ->setOrganisationNature(OrganisationNature::CLUB)
        ->addSupportedDiscipline(Discipline::FOOTBALL, Gender::FEMALE)
        // ....
    ;

    /** @var OrganisationLocalType $organisationType */
    $organisationType = $organisationBuilder->getOrganisationType();

# 9. Known issues

## 9.1 SSL certificate verification issue

For a PHP installation that doesn’t come with curl libraries bundled, like the Windows PHP distribution, you need to download the certificate file and tell PHP where to find it. Then you need to edit `php.ini` file by adding a path in following setting:

    curl.cainfo=c:\php\cacert.pem