├── .github
└── workflows
│ └── ci.yaml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── UPGRADE.md
├── composer.json
├── doc
├── activity_profile.md
├── agent_profile.md
├── client.md
├── index.md
├── state.md
└── statements.md
├── phpspec.yml.dist
├── phpunit.xml.dist
├── spec
├── Request
│ └── HandlerSpec.php
├── XApiClientBuilderSpec.php
└── XApiClientSpec.php
├── src
├── Api
│ ├── ActivityProfileApiClient.php
│ ├── ActivityProfileApiClientInterface.php
│ ├── AgentProfileApiClient.php
│ ├── AgentProfileApiClientInterface.php
│ ├── DocumentApiClient.php
│ ├── StateApiClient.php
│ ├── StateApiClientInterface.php
│ ├── StatementsApiClient.php
│ └── StatementsApiClientInterface.php
├── Http
│ └── MultipartStatementBody.php
├── Request
│ ├── Handler.php
│ └── HandlerInterface.php
├── XApiClient.php
├── XApiClientBuilder.php
├── XApiClientBuilderInterface.php
└── XApiClientInterface.php
└── tests
└── Api
├── ActivityProfileApiClientTest.php
├── AgentProfileApiClientTest.php
├── ApiClientTest.php
├── StateApiClientTest.php
└── StatementsApiClientTest.php
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: 'CI'
2 |
3 | on:
4 | - 'push'
5 | - 'pull_request'
6 |
7 | jobs:
8 | tests:
9 | name: 'Tests'
10 |
11 | runs-on: 'ubuntu-latest'
12 |
13 | strategy:
14 | matrix:
15 | include:
16 | - php-version: '7.1'
17 | composer-options: '--prefer-stable'
18 | - php-version: '7.1'
19 | composer-options: '--prefer-lowest --prefer-stable'
20 | - php-version: '7.2'
21 | composer-options: '--prefer-stable'
22 | - php-version: '7.3'
23 | composer-options: '--prefer-stable'
24 |
25 | steps:
26 | - name: 'Check out'
27 | uses: 'actions/checkout@v2'
28 |
29 | - name: 'Set up PHP'
30 | uses: 'shivammathur/setup-php@v2'
31 | with:
32 | php-version: '${{ matrix.php-version }}'
33 | coverage: 'none'
34 |
35 | - name: 'Get Composer cache directory'
36 | id: 'composer-cache'
37 | run: 'echo "::set-output name=cache-dir::$(composer config cache-files-dir)"'
38 |
39 | - name: 'Cache dependencies'
40 | uses: 'actions/cache@v2'
41 | with:
42 | path: '${{ steps.composer-cache.outputs.cache-dir }}'
43 | key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
44 | restore-keys: 'php-${{ matrix.php-version }}-composer-locked-'
45 |
46 | - name: 'Install dependencies'
47 | run: 'composer update --no-progress $COMPOSER_OPTIONS'
48 |
49 | - name: 'Install PHPUnit'
50 | run: 'vendor/bin/simple-phpunit install'
51 |
52 | - name: 'Run PhpSpec'
53 | run: 'vendor/bin/phpspec run'
54 |
55 | - name: 'Run PHPUnit'
56 | run: 'SYMFONY_DEPRECATIONS_HELPER=weak vendor/bin/simple-phpunit'
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.phpunit.result.cache
2 | /composer.lock
3 | /phpunit.xml
4 | /vendor
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | CHANGELOG
2 | =========
3 |
4 | 0.7.0
5 | -----
6 |
7 | * dropped support for PHP < 7.1
8 |
9 | 0.6.0
10 | -----
11 |
12 | * added compatibility with HTTPlug 2
13 |
14 | * dropped the support for HHVM and for PHP < 5.6
15 |
16 | * updated the `X-Experience-API-Version` header to default to the latest patch
17 | version (`1.0.3`)
18 |
19 | * allow `2.x` releases of the `php-xapi/model` package too
20 |
21 | * allow `3.x` releases of the `php-xapi/model` package for PHP 7.2 compatibility
22 |
23 | 0.5.0
24 | -----
25 |
26 | * **CAUTION**: This release drops support for PHP 5.3 due to the introduced
27 | dependency on `php-http/httplug` (see below).
28 |
29 | * The client now depends on the [HTTPlug library](http://httplug.io/) to
30 | perform HTTP requests. This means that the package now depends the virtual
31 | `php-http/client-implementation`. To satisfy this dependency you have to
32 | pick [an implementation](https://packagist.org/providers/php-http/client-implementation)
33 | and install it together with `php-xapi/client`.
34 |
35 | For example, if you prefer to use [Guzzle 6](http://docs.guzzlephp.org/en/latest/)
36 | you would do the following:
37 |
38 | ```bash
39 | $ composer require --no-update php-http/guzzle6-adapter
40 | $ composer require php-xapi/client
41 | ```
42 |
43 | * The `setHttpClient()` and `setRequestFactory()` method have been added
44 | to the `XApiClientBuilderInterface` and must be used to configure the
45 | `HttpClient` and `RequestFactory` instances you intend to use.
46 |
47 | To use [Guzzle 6](http://docs.guzzlephp.org/en/latest/), for example,
48 | this will look like this:
49 |
50 | ```php
51 | use Http\Adapter\Guzzle6\Client;
52 | use Http\Message\MessageFactory\GuzzleMessageFactory;
53 | use Xabbuh\XApi\Client\XApiClientBuilder;
54 |
55 | $builder = new XApiClientBuilder();
56 | $client = $builder->setHttpClient(new Client())
57 | ->setRequestFactory(new GuzzleMessageFactory())
58 | ->setBaseUrl('http://example.com/xapi/')
59 | ->build();
60 | ```
61 |
62 | You can avoid calling `setHttpClient()` and `setRequestFactory` by installing
63 | the [HTTP discovery](http://php-http.org/en/latest/discovery.html) package.
64 |
65 | * The `xabbuh/oauth1-authentication` package now must be installed if you want
66 | to use OAuth1 authentication.
67 |
68 | * Bumped the required versions of all `php-xapi` packages to the `1.x` release
69 | series.
70 |
71 | * Include the raw attachment content wrapped in a `multipart/mixed` encoded
72 | request when raw content is part of a statement's attachment.
73 |
74 | * Added the possibility to decide whether or not to include attachments when
75 | requesting statements from an LRS. A second optional `$attachments` argument
76 | (defaulting to `true`) has been added for this purpose to the `getStatement()`,
77 | `getVoidedStatement()`, and `getStatements()` methods of the `StatementsApiClient`
78 | class and the `StatementsApiClientInterface`.
79 |
80 | * An optional fifth `$headers` parameter has been added to the `createRequest()`
81 | method of the `HandlerInterface` and the `Handler` class which allows to pass
82 | custom headers when performing HTTP requests.
83 |
84 | 0.4.0
85 | -----
86 |
87 | * The `XApiClientBuilder` class now makes use of the `SerializerFactoryInterface`
88 | introduced in release `0.4.0` of the `php-xapi/serializer` package. By
89 | default, it will fall back to the `SerializerFactory` implemented provided
90 | by the `php-xapi/symfony-serializer` to maintain backwards-compatibility
91 | with the previous release. However, you are now able to inject arbitrary
92 | implementations of the `SerializerFactoryInterface` into the constructor
93 | of the `XApiClientBuilder` to use whatever alternative implementation
94 | (packages providing such an implementation should provide the virtual
95 | `php-xapi/serializer-implementation` package).
96 |
97 | 0.3.0
98 | -----
99 |
100 | * Do not send authentication headers when no credentials have been configured.
101 |
102 | * Fixed treating HTTP methods case insensitive. Rejecting uppercased HTTP
103 | method names contradicts the HTTP specification. Lowercased method names
104 | will still be supported to keep backwards compatibility though.
105 |
106 | * Fixed creating `XApiClient` instances in an invalid state. The `XApiClientBuilder`
107 | now throws a `\LogicException` when the `build()` method is called before
108 | a base URI was configured.
109 |
110 | * Removed the `ApiClient` class. The `$requestHandler` and `$version` attributes
111 | have been moved to the former child classes of the `ApiClient` class and
112 | their visibility has been changed to `private`.
113 |
114 | * The visibility of the `$documentDataSerializer` property of the `ActivityProfileApiClient`,
115 | `AgentProfileApiClient`, `DocumentApiClient`, and `StateApiClient` classes
116 | has been changed to `private`.
117 |
118 | * Removed the `getRequestHandler()` method from the API classes:
119 |
120 | * `ActivityProfileApiClient::getRequestHandler()`
121 | * `AgentProfileApiClient::getRequestHandler()`
122 | * `ApiClient::getRequestHandler()`
123 | * `DocumentApiClient::getRequestHandler()`
124 | * `StateApiClient::getRequestHandler()`
125 | * `StatementsApiClient::getRequestHandler()`
126 |
127 | * Removed the `getVersion()` method from the API interfaces:
128 |
129 | * `ActivityProfileApiClientInterface::getVersion()`
130 | * `AgentProfileApiClientInterface::getVersion()`
131 | * `StateApiClientInterface::getVersion()`
132 | * `StatementsApiClientInterface::getVersion()`
133 |
134 | * Removed the `getVersion()` method from the API classes:
135 |
136 | * `ActivityProfileApiClient::getVersion()`
137 | * `AgentProfileApiClient::getVersion()`
138 | * `ApiClient::getVersion()`
139 | * `DocumentApiClient::getVersion()`
140 | * `StateApiClient::getVersion()`
141 | * `StatementsApiClient::getVersion()`
142 | * `XApiClient::getVersion()`
143 |
144 | * Removed the `getUsername()` and `getPassword()` methods from the `HandlerInterface`
145 | and the `Handler` class.
146 |
147 | * Removed the `getHttpClient()` method from the `Handler` class.
148 |
149 | * Removed the `getSerializerRegistry()` method from the `XApiClient` class.
150 |
151 | * Made all classes final.
152 |
153 | 0.2.0
154 | -----
155 |
156 | * made the client compatible with version 0.5 of the `php-xapi/model` package
157 |
158 | * made the client compatible with version 0.3 of the `php-xapi/serializer` package
159 |
160 | 0.1.0
161 | -----
162 |
163 | First release of an Experience API client based on the Guzzle HTTP library.
164 |
165 | This package replaces the `xabbuh/xapi-client` package which is now deprecated
166 | and should no longer be used.
167 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014-2019 Christian Flothmann
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | PHP xApi (Experience API) Client
2 | ================================
3 |
4 | [](https://travis-ci.org/php-xapi/client)
5 | [](https://scrutinizer-ci.com/g/php-xapi/client/?branch=master)
6 | [](https://scrutinizer-ci.com/g/php-xapi/client/?branch=master)
7 |
8 | Client side PHP implementation of the
9 | [Experience API](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md).
10 |
11 | Installation
12 | ------------
13 |
14 | The recommended way to install the xAPI client is using
15 | [Composer](http://getcomposer.org/):
16 |
17 | 1. Add ``php-xapi/client`` as a dependency to your project:
18 |
19 | ```bash
20 | $ composer require php-xapi/client
21 | ```
22 |
23 | 1. Require Composer's autoloader:
24 |
25 | ``` php
26 | require __DIR__.'/vendor/autoload.php';
27 | ```
28 |
29 | Usage
30 | -----
31 |
32 | Read the [documentation](doc/index.md) to find out how to use the library.
33 |
34 | Issues
35 | ------
36 |
37 | Report issues in the [issue tracker of this package](https://github.com/php-xapi/client/issues).
38 |
39 | License
40 | -------
41 |
42 | This package is under the MIT license. See the complete license in the
43 | [LICENSE](LICENSE) file.
44 |
--------------------------------------------------------------------------------
/UPGRADE.md:
--------------------------------------------------------------------------------
1 | UPGRADE
2 | =======
3 |
4 | Upgrading from 0.4 to 0.5
5 | -------------------------
6 |
7 | * **CAUTION**: This release drops support for PHP 5.3 due to the introduced
8 | dependency on `php-http/httplug` (see below).
9 |
10 | * The client now depends on the [HTTPlug library](http://httplug.io/) to
11 | perform HTTP requests. This means that the package now depends the virtual
12 | `php-http/client-implementation`. To satisfy this dependency you have to
13 | pick [an implementation](https://packagist.org/providers/php-http/client-implementation)
14 | and install it together with `php-xapi/client`.
15 |
16 | For example, if you prefer to use [Guzzle 6](http://docs.guzzlephp.org/en/latest/)
17 | you would do the following:
18 |
19 | ```bash
20 | $ composer require --no-update php-http/guzzle6-adapter
21 | $ composer require php-xapi/client
22 | ```
23 |
24 | * The `setHttpClient()` and `setRequestFactory()` method have been added
25 | to the `XApiClientBuilderInterface` and must be used to configure the
26 | `HttpClient` and `RequestFactory` instances you intend to use.
27 |
28 | To use [Guzzle 6](http://docs.guzzlephp.org/en/latest/), for example,
29 | this will look like this:
30 |
31 | ```php
32 | use Http\Adapter\Guzzle6\Client;
33 | use Http\Message\MessageFactory\GuzzleMessageFactory;
34 | use Xabbuh\XApi\Client\XApiClientBuilder;
35 |
36 | $builder = new XApiClientBuilder();
37 | $client = $builder->setHttpClient(new Client())
38 | ->setRequestFactory(new GuzzleMessageFactory())
39 | ->setBaseUrl('http://example.com/xapi/')
40 | ->build();
41 | ```
42 |
43 | You can avoid calling `setHttpClient()` and `setRequestFactory` by installing
44 | the [HTTP discovery](http://php-http.org/en/latest/discovery.html) package.
45 |
46 | * The `xabbuh/oauth1-authentication` package now must be installed if you want
47 | to use OAuth1 authentication.
48 |
49 | * A second optional `$attachments` argument (defaulting to `true`) has been added
50 | to the `getStatement()`, `getVoidedStatement()`, and `getStatements()` methods
51 | of the `StatementsApiClient` class and the `StatementsApiClientInterface`.
52 |
53 | * An optional fifth `$headers` parameter has been added to the `createRequest()`
54 | method of the `HandlerInterface` and the `Handler` class which allows to pass
55 | custom headers when performing HTTP requests.
56 |
57 | Upgrading from 0.2 to 0.3
58 | -------------------------
59 |
60 | * Removed the `ApiClient` class. The `$requestHandler` and `$version` attributes
61 | have been moved to the former child classes of the `ApiClient` class and
62 | their visibility has been changed to `private`.
63 |
64 | * The visibility of the `$documentDataSerializer` property of the `ActivityProfileApiClient`,
65 | `AgentProfileApiClient`, `DocumentApiClient`, and `StateApiClient` classes
66 | has been changed to `private`.
67 |
68 | * Removed the `getRequestHandler()` method from the API classes:
69 |
70 | * `ActivityProfileApiClient::getRequestHandler()`
71 | * `AgentProfileApiClient::getRequestHandler()`
72 | * `ApiClient::getRequestHandler()`
73 | * `DocumentApiClient::getRequestHandler()`
74 | * `StateApiClient::getRequestHandler()`
75 | * `StatementsApiClient::getRequestHandler()`
76 | * `XApiClient::getRequestHandler()`
77 |
78 | * Removed the `getVersion()` method from the API interfaces:
79 |
80 | * `ActivityProfileApiClientInterface::getVersion()`
81 | * `AgentProfileApiClientInterface::getVersion()`
82 | * `StateApiClientInterface::getVersion()`
83 | * `StatementsApiClientInterface::getVersion()`
84 |
85 | * Removed the `getVersion()` method from the API classes:
86 |
87 | * `ActivityProfileApiClient::getVersion()`
88 | * `AgentProfileApiClient::getVersion()`
89 | * `ApiClient::getVersion()`
90 | * `DocumentApiClient::getVersion()`
91 | * `StateApiClient::getVersion()`
92 | * `StatementsApiClient::getVersion()`
93 | * `XApiClient::getVersion()`
94 |
95 | * Removed the `getUsername()` and `getPassword()` methods from the `HandlerInterface`
96 | and the `Handler` class.
97 |
98 | * Removed the `getHttpClient()` method from the `Handler` class.
99 |
100 | * Removed the `getSerializerRegistry()` method from the `XApiClient` class.
101 |
102 | * All classes are final now which means that you can now longer extend them.
103 | Consider using composition/decoration instead if you need to build functionality
104 | on top of the built-in classes.
105 |
106 | Upgrading from 0.1 to 0.2
107 | -------------------------
108 |
109 | * Statement identifiers must be passed as `StatementId` objects instead of
110 | strings.
111 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "php-xapi/client",
3 | "type": "library",
4 | "description": "client library for the Experience API (xAPI)",
5 | "keywords": ["xAPI", "Experience API", "Tin Can API", "client"],
6 | "homepage": "https://github.com/php-xapi/client",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Christian Flothmann",
11 | "homepage": "https://github.com/xabbuh"
12 | }
13 | ],
14 | "require": {
15 | "php": "^7.1",
16 | "php-http/client-common": "^1.0 || ^2.0",
17 | "php-http/client-implementation": "^1.0",
18 | "php-http/httplug": "^1.0 || ^2.0",
19 | "php-http/message": "^1.0",
20 | "php-http/message-factory": "^1.0",
21 | "php-xapi/exception": "^0.1 || ^0.2",
22 | "php-xapi/model": "^1.0 || ^2.0 || ^3.0",
23 | "php-xapi/serializer": "^2.0",
24 | "php-xapi/serializer-implementation": "^2.0",
25 | "php-xapi/symfony-serializer": "^2.0",
26 | "psr/http-message": "^1.0"
27 | },
28 | "require-dev": {
29 | "phpspec/phpspec": "^2.4",
30 | "php-http/mock-client": "^1.2",
31 | "php-xapi/test-fixtures": "^1.0",
32 | "symfony/phpunit-bridge": "^5.2"
33 | },
34 | "minimum-stability": "dev",
35 | "suggest": {
36 | "php-http/discovery": "For automatic discovery of HTTP clients and request factories",
37 | "xabbuh/oauth1-authentication": "For OAuth1 authentication support"
38 | },
39 | "conflict": {
40 | "xabbuh/xapi-client": "*"
41 | },
42 | "autoload": {
43 | "psr-4": {
44 | "Xabbuh\\XApi\\Client\\": "src/"
45 | }
46 | },
47 | "autoload-dev": {
48 | "psr-4": {
49 | "spec\\Xabbuh\\XApi\\Client\\": "spec/",
50 | "Xabbuh\\XApi\\Client\\Tests\\": "tests/"
51 | }
52 | },
53 | "extra": {
54 | "branch-alias": {
55 | "dev-master": "0.7.x-dev"
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/doc/activity_profile.md:
--------------------------------------------------------------------------------
1 | The Activity Profile API
2 | ========================
3 |
4 | Activity Profiles
5 | -----------------
6 |
7 | A LMS can use the xAPI to store documents associated to a certain activity using
8 | activity profiles. An activity profile is dedicated to an activity and a profile
9 | id:
10 |
11 | ```php
12 | use Xabbuh\XApi\Model\ActivityProfile;
13 |
14 | // ...
15 | $profile = new ActivityProfile();
16 | $profile->setActivity($activity);
17 | $profile->setProfileId($profileId);
18 | ```
19 |
20 | Documents
21 | ---------
22 |
23 | Documents are simple collections of key-value pairs and can be accessed like arrays:
24 |
25 | ```php
26 | use Xabbuh\XApi\Model\ActivityProfileDocument;
27 |
28 | // ...
29 | $document = new ActivityProfileDocument();
30 | $document->setActivityProfile($profile);
31 | $document['x'] = 'foo';
32 | $document['y'] = 'bar';
33 | ```
34 |
35 | Obtaining the Activity Profile API Client
36 | -----------------------------------------
37 |
38 | After you have [built the global xAPI client](client.md), you can obtain an activity
39 | profile API client by calling its ``getActivityProfileApiClient()`` method:
40 |
41 | ```php
42 | $activityProfileApiClient = $xApiClient->getActivityProfileApiClient();
43 | ```
44 |
45 | Storing Activity Profile Documents
46 | ----------------------------------
47 |
48 | You can simply store an ``ActivityProfileDocument`` passing it to the
49 | ``createOrUpdateActivityProfileDocument()`` method of the xAPI client:
50 |
51 | ```php
52 | $document = ...; // the activity profile document
53 | $activityProfileApiClient->createOrUpdateActivityProfileDocument($document);
54 | ```
55 |
56 | If a document already exists for this activity profile, the existing document will
57 | be updated. This means that new fields will be updated, existing fields that are
58 | included in the new document will be overwritten and existing fields that are
59 | not included in the new document will be kept as they are.
60 |
61 | If you want to replace a document, use the ``createOrReplaceActivityProfileDocument()``
62 | method instead:
63 |
64 | ```php
65 | $document = ...; // the activity profile document
66 | $activityProfileApiClient->createOrReplaceActivityProfileDocument($document);
67 | ```
68 |
69 | Deleting Activity Profile Documents
70 | -----------------------------------
71 |
72 | An ``ActivityProfileDocument`` is deleted by passing the particular ``ActivityProfile``
73 | to the ``deleteActivityProfileDocument()`` method:
74 |
75 | ```php
76 | $profile = ...; // the activity profile the document should be deleted from
77 | $activityProfileApiClient->deleteActivityProfileDocument($profile);
78 | ```
79 |
80 | Retrieving Activity Profile Documents
81 | -------------------------------------
82 |
83 | Similarly, you receive a document for a particular activity profile by passing
84 | the profile to the ``getActivityProfileDocument()`` method:
85 |
86 | ```php
87 | $profile = ...; // the activity profile the document should be retrieved from
88 | $document = $activityProfileApiClient->getActivityProfileDocument($profile);
89 | ```
90 |
--------------------------------------------------------------------------------
/doc/agent_profile.md:
--------------------------------------------------------------------------------
1 | The Agent Profile API
2 | =====================
3 |
4 | Agent Profiles
5 | --------------
6 |
7 | A LMS can use the xAPI to store documents associated to a certain agent using
8 | agent profiles. An agent profile is dedicated to an agent and a profile id:
9 |
10 | ```php
11 | use Xabbuh\XApi\Model\AgentProfile;
12 |
13 | // ...
14 | $profile = new AgentProfile();
15 | $profile->setAgent($agent);
16 | $profile->setProfileId($profileId);
17 | ```
18 |
19 | Documents
20 | ---------
21 |
22 | Documents are simple collections of key-value pairs and can be accessed like arrays:
23 |
24 | ```php
25 | use Xabbuh\XApi\Model\AgentProfileDocument;
26 |
27 | // ...
28 | $document = new AgentProfileDocument();
29 | $document->setAgentProfile($profile);
30 | $document['x'] = 'foo';
31 | $document['y'] = 'bar';
32 | ```
33 |
34 | Obtaining the Agent Profile API Client
35 | --------------------------------------
36 |
37 | After you have [built the global xAPI client](client.md), you can obtain an agent
38 | profile API client by calling its ``getAgentProfileApiClient()`` method:
39 |
40 | ```php
41 | $agentProfileApiClient = $xApiClient->getAgentProfileApiClient();
42 | ```
43 |
44 | Storing Agent Profile Documents
45 | -------------------------------
46 |
47 | You can simply store an ``AgentProfileDocument`` passing it to the
48 | ``createOrUpdateAgentProfileDocument()`` method of the xAPI client:
49 |
50 | ```php
51 | $document = ...; // the agent profile document
52 | $agentProfileApiClient->createOrUpdateAgentProfileDocument($document);
53 | ```
54 |
55 | If a document already exists for this agent profile, the existing document will
56 | be updated. This means that new fields will be updated, existing fields that are
57 | included in the new document will be overwritten and existing fields that are
58 | not included in the new document will be kept as they are.
59 |
60 | If you want to replace a document, use the ``createOrReplaceAgentProfileDocument()``
61 | method instead:
62 |
63 | ```php
64 | $document = ...; // the agent profile document
65 | $agentProfileApiClient->createOrReplaceAgentProfileDocument($document);
66 | ```
67 |
68 | Deleting Agent Profile Documents
69 | --------------------------------
70 |
71 | An ``AgentProfileDocument`` is deleted by passing the particular ``AgentProfile``
72 | to the ``deleteAgentProfileDocument()`` method:
73 |
74 | ```php
75 | $profile = ...; // the agent profile the document should be deleted from
76 | $agentProfileApiClient->deleteAgentProfileDocument($profile);
77 | ```
78 |
79 | Retrieving Agent Profile Documents
80 | ----------------------------------
81 |
82 | Similarly, you receive a document for a particular agent profile by passing the
83 | profile to the ``getAgentProfileDocument()`` method:
84 |
85 | ```php
86 | $profile = ...; // the agent profile the document should be retrieved from
87 | $document = $agentProfileApiClient->getAgentProfileDocument($profile);
88 | ```
89 |
--------------------------------------------------------------------------------
/doc/client.md:
--------------------------------------------------------------------------------
1 | Building an xAPI Client
2 | =======================
3 |
4 | The xAPI client library ships with a builder class which eases the process of
5 | creating an instance of an ``XApiClient`` class:
6 |
7 | ```php
8 | use Xabbuh\XApi\Client\XApiClientBuilder;
9 |
10 | $builder = new XApiClientBuilder();
11 | $xApiClient = $builder->setBaseUrl('http://example.com/lrs/api')
12 | ->setVersion('1.0.0')
13 | ->build();
14 | ```
15 |
16 | The builder creates a client for the 1.0.1 API version if you don't set a version.
17 |
18 | HTTP Basic Authentication
19 | -------------------------
20 |
21 | Use the ``setAuth()`` method if access to the LRS resources is protected with
22 | HTTP Basic authentication:
23 |
24 | ```php
25 | use Xabbuh\XApi\Client\XApiClientBuilder;
26 |
27 | $builder = new XApiClientBuilder();
28 | $xApiClient = $builder->setBaseUrl('http://example.com/lrs/api')
29 | ->setAuth('username', 'password')
30 | ->build();
31 | ```
32 |
33 | OAuth1 Authentication
34 | ---------------------
35 |
36 | Using the ``setOAuthCredentials()`` method, you can configure the client to
37 | access OAuth1 protected resources:
38 |
39 | ```php
40 | use Xabbuh\XApi\Client\XApiClientBuilder;
41 |
42 | $builder = new XApiClientBuilder();
43 | $xApiClient = $builder->setBaseUrl('http://example.com/lrs/api')
44 | ->setOAuthCredentials('consumer-key', 'consumer-secret', 'token', 'token-secret')
45 | ->build();
46 | ```
47 |
48 | Using the APIs
49 | --------------
50 |
51 | The Experience API consists of four sub APIs: the statements API, the state API,
52 | the activity profile API and the agent profile API. A client for each of these
53 | APIs can be obtained from the global ``XApiClient`` instance:
54 |
55 | ```php
56 | $statementsApiClient = $xApiClient->getStatementsApiClient();
57 | $stateApiClient = $xApiClient->getStateApiClient();
58 | $activityProfileApiClient = $xApiClient->getActivityProfileApiClient();
59 | $agentProfileApiClient = $xApiClient->getAgentProfileApiClient();
60 | ```
61 |
62 | Read the dedicated chapters of the sub APIs to learn how to make use of them:
63 |
64 | 1. [The Statements API](statements.md)
65 |
66 | 1. [The State API](state.md)
67 |
68 | 1. [The Activity Profile API](activity_profile.md)
69 |
70 | 1. [The Agent profile API](agent_profile.md)
71 |
--------------------------------------------------------------------------------
/doc/index.md:
--------------------------------------------------------------------------------
1 | Documentation
2 | =============
3 |
4 | 1. [Obtaining an xAPI client](client.md)
5 |
6 | 1. [The Statements API](statements.md)
7 |
8 | 1. [The State API](state.md)
9 |
10 | 1. [The Activity Profile API](activity_profile.md)
11 |
12 | 1. [The Agent profile API](agent_profile.md)
13 |
--------------------------------------------------------------------------------
/doc/state.md:
--------------------------------------------------------------------------------
1 | The State API
2 | =============
3 |
4 | States
5 | ------
6 |
7 | A LMS can use the xAPI to store documents associated to a certain state. A state
8 | is dedicated to an activity, an actor, a state id and an optional registration
9 | id (for example a user id):
10 |
11 | ```php
12 | use Xabbuh\XApi\Model\State;
13 |
14 | // ...
15 | $state = new State();
16 | $state->setActivity($activity);
17 | $state->setActor($actor);
18 | $state->setStateId($stateId);
19 | ```
20 |
21 | Documents
22 | ---------
23 |
24 | Documents are simple collections of key-value pairs and can be accessed like arrays:
25 |
26 | ```php
27 | use Xabbuh\XApi\Model\StateDocument;
28 |
29 | // ...
30 | $document = new StateDocument();
31 | $document->setState($state);
32 | $document['x'] = 'foo';
33 | $document['y'] = 'bar';
34 | ```
35 |
36 | Obtaining the State API Client
37 | ------------------------------
38 |
39 | After you have [built the global xAPI client](client.md), you can obtain a state
40 | API client by calling its ``getStateApiClient()`` method:
41 |
42 | ```php
43 | $stateApiClient = $xApiClient->getStateApiClient();
44 | ```
45 |
46 | Storing State Documents
47 | -----------------------
48 |
49 | You can simply store a ``StateDocument`` passing it to the ``createOrUpdateStateDocument()``
50 | method of the xAPI client:
51 |
52 | ```php
53 | $document = ...; // the state document
54 | $stateApiClient->createOrUpdateStateDocument($document);
55 | ```
56 |
57 | If a document already exists for this state, the existing document will be updated.
58 | This means that new fields will be updated, existing fields that are included in
59 | the new document will be overwritten and existing fields that are not included in
60 | the new document will be kept as they are.
61 |
62 | If you want to replace a document, use the ``createOrReplaceStateDocument()`` method
63 | instead:
64 |
65 | ```php
66 | $document = ...; // the state document
67 | $stateApiClient->createOrReplaceStateDocument($document);
68 | ```
69 |
70 | Deleting State Documents
71 | ------------------------
72 |
73 | A ``StateDocument`` is deleted by passing the particular ``State`` to the ``deleteStateDocument()``
74 | method:
75 |
76 | ```php
77 | $state = ...; // the state the document should be deleted from
78 | $stateApiClient->deleteStateDocument($state);
79 | ```
80 |
81 | Retrieving State Documents
82 | --------------------------
83 |
84 | Similarly, you receive a document for a particular state by passing the state to
85 | the ``getStateDocument()`` method:
86 |
87 | ```php
88 | $state = ...; // the state the document should be retrieved from
89 | $document = $stateApiClient->getStateDocument($state);
90 | ```
91 |
--------------------------------------------------------------------------------
/doc/statements.md:
--------------------------------------------------------------------------------
1 | The Statements API
2 | ==================
3 |
4 | Obtaining the Agent Profile API Client
5 | --------------------------------------
6 |
7 | After you have [built the global xAPI client](client.md), you can obtain a statements
8 | API client by calling its ``getStatementsApiClient()`` method:
9 |
10 | ```php
11 | $statementsApiClient = $xApiClient->getStatementsApiClient();
12 | ```
13 |
14 | Storing Statements
15 | ------------------
16 |
17 | The ``storeStatement()`` and ``storeStatements()`` methods can be used to store
18 | a single Statement or a collection of Statements. Both method return the stored
19 | Statement(s) each having a unique id created by the remote LRS.
20 |
21 | ```php
22 |
23 | use Xabbuh\XApi\Model\Statement;
24 |
25 | $statement = new Statement();
26 | // ...
27 |
28 | // store a single Statement
29 | $statementsApiClient->storeStatement($statement);
30 |
31 | $statement2 = new Statement();
32 | // ...
33 |
34 | // store a collection of clients
35 | $statementsApiClient->storeStatements(array($statement, $statement2));
36 | ```
37 |
38 | Retrieving Statements
39 | ---------------------
40 |
41 | Use the ``getStatement()`` method to obtain a certain Statement given its id:
42 |
43 | ```php
44 | // ...
45 |
46 | // get a single Statement
47 | $statement = $statementsApiClient->getStatement($statementId);
48 | ```
49 |
50 | ``getStatements()`` returns a collection of Statements encapsulated in a
51 | StatementResult instance:
52 |
53 | ```php
54 | // ...
55 |
56 | // returns all accessible Statements
57 | $result = $statementsApiClient->getStatements();
58 | ```
59 |
60 | You can even filter Statements using a StatementFilter:
61 |
62 | ```php
63 | use Xabbuh\XApi\Model\StatementsFilter;
64 |
65 | // ...
66 | $filter = new StatementsFilter();
67 | $filter
68 | ->byActor($actor) // filter by Actor
69 | ->byVerb($verb) // filter by Verb
70 | ->byActivity($activity) // filter by Activity
71 | ->byRegistration(...) // filter for Statements matching the given
72 | // registration id
73 | ->enableRelatedActivityFilter() // apply the Activity filter to Sub-Statements
74 | ->disableRelatedActivityFilter() // apply the Activity filter to Sub-Statements
75 | ->enableRelatedAgentFilter() // apply the Agent filter to Sub-Statements
76 | ->disableRelatedAgentFilter() // apply the Agent filter to Sub-Statements
77 | ->since(new \DateTime(...)) // filter for Statements stored since
78 | // the given timestamp
79 | ->until(new \DateTime(...)) // filter for Statements stored before
80 | // the given timestamp
81 | ->limit(5) // limit the number of Statements returned
82 | ->format(...) // the result format (one of "ids", "exact",
83 | // "canonical")
84 | ->includeAttachments() // return Statements with attachments included
85 | ->excludeAttachments() // return Statements without attachments
86 | ->ascending() // ascending order of stored time
87 | ->descending(); // ascending order of stored time
88 |
89 | $result = $statementsApiClient->getStatements($filter->getFilter());
90 | ```
91 |
92 | If you limited the number of returned results, you can get the next Statements
93 | by calling the ``getNextStatements()`` method passing the ``StatementResult``
94 | of the previous request to it:
95 |
96 | ```php
97 | // ....
98 | $filter = new StatementsFilter();
99 | $filter->limit(3);
100 | $firstStatementResult = $statementsApiClient->getStatements($filter);
101 |
102 | // get the next Statements
103 | $nextStatementResult = $statementsApiClient->getNextStatements($firstStatementResult);
104 | ```
105 |
106 | The Experience API doesn't allow to delete Statements. You have to mark them as
107 | voided instead:
108 |
109 | ```php
110 | // ...
111 | $statement = ...; // The Statement being voided
112 | $actor = ...; // The Actor voiding the Statement
113 | $statementsApiClient->voidStatement($statement, $actor);
114 | ```
115 |
116 | Voided Statements won't be returned when requesting either a single Statement or
117 | a collection of Statements. Though, you can retrieve a single voided Statement
118 | using the ``getVoidedStatement()`` method:
119 |
120 | ```php
121 | // ...
122 | $voidedStatement = $statementsApiClient->getVoidedStatement($statementId);
123 | ```
124 |
--------------------------------------------------------------------------------
/phpspec.yml.dist:
--------------------------------------------------------------------------------
1 | suites:
2 | default:
3 | namespace: Xabbuh\XApi\Client
4 | psr4_prefix: Xabbuh\XApi\Client
5 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./tests
6 |
7 |
8 |
9 |
10 | ./src
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/spec/Request/HandlerSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith($client, $requestFactory, 'http://example.com/xapi/', '1.0.1');
20 | }
21 |
22 | function it_throws_an_exception_if_a_request_is_created_with_an_invalid_method()
23 | {
24 | $this->shouldThrow('\InvalidArgumentException')->during('createRequest', array('options', '/xapi/statements'));
25 | }
26 |
27 | function it_returns_get_request_created_by_the_http_client(RequestFactory $requestFactory, RequestInterface $request)
28 | {
29 | $requestFactory->createRequest('GET', 'http://example.com/xapi/statements', array(
30 | 'X-Experience-API-Version' => '1.0.1',
31 | 'Content-Type' => 'application/json',
32 | ), null)->willReturn($request);
33 |
34 | $this->createRequest('get', '/statements')->shouldReturn($request);
35 | $this->createRequest('GET', '/statements')->shouldReturn($request);
36 | }
37 |
38 | function it_returns_post_request_created_by_the_http_client(RequestFactory $requestFactory, RequestInterface $request)
39 | {
40 | $requestFactory->createRequest('POST', 'http://example.com/xapi/statements', array(
41 | 'X-Experience-API-Version' => '1.0.1',
42 | 'Content-Type' => 'application/json',
43 | ), 'body')->willReturn($request);
44 |
45 | $this->createRequest('post', '/statements', array(), 'body')->shouldReturn($request);
46 | $this->createRequest('POST', '/statements', array(), 'body')->shouldReturn($request);
47 | }
48 |
49 | function it_returns_put_request_created_by_the_http_client(RequestFactory $requestFactory, RequestInterface $request)
50 | {
51 | $requestFactory->createRequest('PUT', 'http://example.com/xapi/statements', array(
52 | 'X-Experience-API-Version' => '1.0.1',
53 | 'Content-Type' => 'application/json',
54 | ), 'body')->willReturn($request);
55 |
56 | $this->createRequest('put', '/statements', array(), 'body')->shouldReturn($request);
57 | $this->createRequest('PUT', '/statements', array(), 'body')->shouldReturn($request);
58 | }
59 |
60 | function it_returns_delete_request_created_by_the_http_client(RequestFactory $requestFactory, RequestInterface $request)
61 | {
62 | $requestFactory->createRequest('DELETE', 'http://example.com/xapi/statements', array(
63 | 'X-Experience-API-Version' => '1.0.1',
64 | 'Content-Type' => 'application/json',
65 | ), null)->willReturn($request);
66 |
67 | $this->createRequest('delete', '/statements')->shouldReturn($request);
68 | $this->createRequest('DELETE', '/statements')->shouldReturn($request);
69 | }
70 |
71 | function it_throws_an_access_denied_exception_when_a_401_status_code_is_returned(HttpClient $client, RequestInterface $request, ResponseInterface $response)
72 | {
73 | $client->sendRequest($request)->willReturn($response);
74 | $response->getStatusCode()->willReturn(401);
75 | $response->getBody()->willReturn('body');
76 |
77 | $this->shouldThrow(AccessDeniedException::class)->during('executeRequest', array($request, array(200)));
78 | }
79 |
80 | function it_throws_an_access_denied_exception_when_a_403_status_code_is_returned(HttpClient $client, RequestInterface $request, ResponseInterface $response)
81 | {
82 | $client->sendRequest($request)->willReturn($response);
83 | $response->getStatusCode()->willReturn(403);
84 | $response->getBody()->willReturn('body');
85 |
86 | $this->shouldThrow(AccessDeniedException::class)->during('executeRequest', array($request, array(200)));
87 | }
88 |
89 | function it_throws_a_not_found_exception_when_a_404_status_code_is_returned(HttpClient $client, RequestInterface $request, ResponseInterface $response)
90 | {
91 | $client->sendRequest($request)->willReturn($response);
92 | $response->getStatusCode()->willReturn(404);
93 | $response->getBody()->willReturn('body');
94 |
95 | $this->shouldThrow(NotFoundException::class)->during('executeRequest', array($request, array(200)));
96 | }
97 |
98 | function it_throws_a_conflict_exception_when_a_409_status_code_is_returned(HttpClient $client, RequestInterface $request, ResponseInterface $response)
99 | {
100 | $client->sendRequest($request)->willReturn($response);
101 | $response->getStatusCode()->willReturn(409);
102 | $response->getBody()->willReturn('body');
103 |
104 | $this->shouldThrow(ConflictException::class)->during('executeRequest', array($request, array(200)));
105 | }
106 |
107 | function it_throws_an_xapi_exception_when_an_unexpected_status_code_is_returned(HttpClient $client, RequestInterface $request, ResponseInterface $response)
108 | {
109 | $client->sendRequest($request)->willReturn($response);
110 | $response->getStatusCode()->willReturn(204);
111 | $response->getBody()->willReturn('body');
112 |
113 | $this->shouldThrow(XApiException::class)->during('executeRequest', array($request, array(200)));
114 | }
115 |
116 | function it_returns_the_response_on_success(HttpClient $client, RequestInterface $request, ResponseInterface $response)
117 | {
118 | $client->sendRequest($request)->willReturn($response);
119 | $response->getStatusCode()->willReturn(200);
120 | $response->getBody()->willReturn('body');
121 |
122 | $this->executeRequest($request, array(200))->shouldReturn($response);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/spec/XApiClientBuilderSpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType(XApiClientBuilderInterface::class);
20 | }
21 |
22 | function it_creates_an_xapi_client(HttpClient $httpClient, RequestFactory $requestFactory)
23 | {
24 | $this->setHttpClient($httpClient);
25 | $this->setRequestFactory($requestFactory);
26 | $this->setBaseUrl('http://example.com/xapi/');
27 | $this->build()->shouldHaveType(XApiClientInterface::class);
28 | }
29 |
30 | function its_methods_can_be_chained(HttpClient $httpClient, RequestFactory $requestFactory)
31 | {
32 | $this->setHttpClient($httpClient)->shouldReturn($this);
33 | $this->setRequestFactory($requestFactory)->shouldReturn($this);
34 | $this->setBaseUrl('http://example.com/xapi/')->shouldReturn($this);
35 | $this->setVersion('1.0.0')->shouldReturn($this);
36 | $this->setAuth('foo', 'bar')->shouldReturn($this);
37 | $this->setOAuthCredentials('consumer key', 'consumer secret', 'token', 'token secret')->shouldReturn($this);
38 | }
39 |
40 | function it_throws_an_exception_if_the_http_client_is_not_configured(RequestFactory $requestFactory)
41 | {
42 | if ($this->isAbleToDiscoverHttpClient()) {
43 | throw new SkippingException('The builder does not throw an exception if it can automatically discover an HTTP client.');
44 | }
45 |
46 | $this->setRequestFactory($requestFactory);
47 | $this->setBaseUrl('http://example.com/xapi/');
48 |
49 | $this->shouldThrow('\LogicException')->during('build');
50 | }
51 |
52 | function it_throws_an_exception_if_the_request_factory_is_not_configured(HttpClient $httpClient)
53 | {
54 | if ($this->isAbleToDiscoverRequestFactory()) {
55 | throw new SkippingException('The builder does not throw an exception if it can automatically discover a request factory.');
56 | }
57 |
58 | $this->setHttpClient($httpClient);
59 | $this->setBaseUrl('http://example.com/xapi/');
60 |
61 | $this->shouldThrow('\LogicException')->during('build');
62 | }
63 |
64 | function it_can_build_the_client_when_it_is_able_to_discover_the_http_client_and_the_request_factory_without_configuring_them_explicitly()
65 | {
66 | if (!class_exists(HttpClientDiscovery::class)) {
67 | throw new SkippingException(sprintf('The "%s" class is required to let the builder auto discover the HTTP client and request factory.', HttpClientDiscovery::class));
68 | }
69 |
70 | if (!$this->isAbleToDiscoverHttpClient()) {
71 | throw new SkippingException('Unable to discover an HTTP client.');
72 | }
73 |
74 | if (!$this->isAbleToDiscoverRequestFactory()) {
75 | throw new SkippingException('Unable to discover a request factory.');
76 | }
77 |
78 | $this->setBaseUrl('http://example.com/xapi/');
79 |
80 | $this->build()->shouldReturnAnInstanceOf(XApiClientInterface::class);
81 | }
82 |
83 | function it_throws_an_exception_if_the_base_uri_is_not_configured(HttpClient $httpClient, RequestFactory $requestFactory)
84 | {
85 | $this->setHttpClient($httpClient);
86 | $this->setRequestFactory($requestFactory);
87 |
88 | $this->shouldThrow('\LogicException')->during('build');
89 | }
90 |
91 | function it_throws_an_exception_when_oauth_credentials_are_configured_but_the_auth_package_is_missing(HttpClient $httpClient, RequestFactory $requestFactory)
92 | {
93 | if (class_exists(OAuth1::class)) {
94 | throw new SkippingException('OAuth1 credentials can be used when the "xabbuh/oauth1-authentication" package is present.');
95 | }
96 |
97 | $this->setHttpClient($httpClient);
98 | $this->setRequestFactory($requestFactory);
99 | $this->setBaseUrl('http://example.com/xapi/');
100 | $this->setOAuthCredentials('consumer_key', 'consumer_secret', 'access_token', 'token_secret');
101 |
102 | $this->shouldThrow(new \LogicException('The "xabbuh/oauth1-authentication package is needed to use OAuth1 authorization.'))->during('build');
103 | }
104 |
105 | function it_accepts_oauth_credentials_when_the_auth_package_is_present(HttpClient $httpClient, RequestFactory $requestFactory)
106 | {
107 | if (!class_exists(OAuth1::class)) {
108 | throw new SkippingException('OAuth1 credentials cannot be used when the "xabbuh/oauth1-authentication" package is missing.');
109 | }
110 |
111 | $this->setHttpClient($httpClient);
112 | $this->setRequestFactory($requestFactory);
113 | $this->setBaseUrl('http://example.com/xapi/');
114 | $this->setOAuthCredentials('consumer_key', 'consumer_secret', 'access_token', 'token_secret');
115 | $this->build();
116 | }
117 |
118 | private function isAbleToDiscoverHttpClient()
119 | {
120 | try {
121 | HttpClientDiscovery::find();
122 |
123 | return true;
124 | } catch (\Exception $e) {
125 | return false;
126 | }
127 | }
128 |
129 | private function isAbleToDiscoverRequestFactory()
130 | {
131 | try {
132 | MessageFactoryDiscovery::find();
133 |
134 | return true;
135 | } catch (\Exception $e) {
136 | return false;
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/spec/XApiClientSpec.php:
--------------------------------------------------------------------------------
1 | getActorSerializer()->willReturn($actorSerializer);
28 | $serializerRegistry->getDocumentDataSerializer()->willReturn($documentDataSerializer);
29 | $serializerRegistry->getStatementSerializer()->willReturn($statementSerializer);
30 | $serializerRegistry->getStatementResultSerializer()->willReturn($statementResultSerializer);
31 |
32 | $this->beConstructedWith($requestHandler, $serializerRegistry, '1.0.1');
33 | }
34 |
35 | function it_returns_a_statements_api_client_instance()
36 | {
37 | $this->getStatementsApiClient()->shouldBeAnInstanceOf(StatementsApiClientInterface::class);
38 | }
39 |
40 | function it_returns_an_activity_profile_api_client_instance()
41 | {
42 | $this->getActivityProfileApiClient()->shouldBeAnInstanceOf(ActivityProfileApiClientInterface::class);
43 | }
44 |
45 | function it_returns_an_agent_profile_api_client_instance()
46 | {
47 | $this->getAgentProfileApiClient()->shouldBeAnInstanceOf(AgentProfileApiClientInterface::class);
48 | }
49 |
50 | function it_returns_a_state_api_client_instance()
51 | {
52 | $this->getStateApiClient()->shouldBeAnInstanceOf(StateApiClientInterface::class);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Api/ActivityProfileApiClient.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Api;
13 |
14 | use Xabbuh\XApi\Model\ActivityProfile;
15 | use Xabbuh\XApi\Model\ActivityProfileDocument;
16 |
17 | /**
18 | * Client to access the activity profile API of an xAPI based learning record
19 | * store.
20 | *
21 | * @author Christian Flothmann
22 | */
23 | final class ActivityProfileApiClient extends DocumentApiClient implements ActivityProfileApiClientInterface
24 | {
25 | /**
26 | * {@inheritDoc}
27 | */
28 | public function createOrUpdateDocument(ActivityProfileDocument $document)
29 | {
30 | $this->doStoreActivityProfileDocument('post', $document);
31 | }
32 |
33 | /**
34 | * {@inheritDoc}
35 | */
36 | public function createOrReplaceDocument(ActivityProfileDocument $document)
37 | {
38 | $this->doStoreActivityProfileDocument('put', $document);
39 | }
40 |
41 | /**
42 | * {@inheritDoc}
43 | */
44 | public function deleteDocument(ActivityProfile $profile)
45 | {
46 | $this->doDeleteDocument('activities/profile', array(
47 | 'activityId' => $profile->getActivity()->getId()->getValue(),
48 | 'profileId' => $profile->getProfileId(),
49 | ));
50 | }
51 |
52 | /**
53 | * {@inheritDoc}
54 | */
55 | public function getDocument(ActivityProfile $profile)
56 | {
57 | /** @var \Xabbuh\XApi\Model\DocumentData $documentData */
58 | $documentData = $this->doGetDocument('activities/profile', array(
59 | 'activityId' => $profile->getActivity()->getId()->getValue(),
60 | 'profileId' => $profile->getProfileId(),
61 | ));
62 |
63 | return new ActivityProfileDocument($profile, $documentData);
64 | }
65 |
66 | /**
67 | * Stores a state document.
68 | *
69 | * @param string $method HTTP method to use
70 | * @param ActivityProfileDocument $document The document to store
71 | */
72 | private function doStoreActivityProfileDocument($method, ActivityProfileDocument $document)
73 | {
74 | $profile = $document->getActivityProfile();
75 | $this->doStoreDocument(
76 | $method,
77 | 'activities/profile',
78 | array(
79 | 'activityId' => $profile->getActivity()->getId()->getValue(),
80 | 'profileId' => $profile->getProfileId(),
81 | ),
82 | $document
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Api/ActivityProfileApiClientInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Api;
13 |
14 | use Xabbuh\XApi\Model\ActivityProfile;
15 | use Xabbuh\XApi\Model\ActivityProfileDocument;
16 |
17 | /**
18 | * Client to access the activity profile API of an xAPI based learning record
19 | * store.
20 | *
21 | * @author Christian Flothmann
22 | */
23 | interface ActivityProfileApiClientInterface
24 | {
25 | /**
26 | * Stores a document for an activity profile. Updates an existing document
27 | * for this activity profile if one exists.
28 | *
29 | * @param ActivityProfileDocument $document The document to store
30 | */
31 | public function createOrUpdateDocument(ActivityProfileDocument $document);
32 |
33 | /**
34 | * Stores a document for an activity profile. Replaces any existing document
35 | * for this activity profile.
36 | *
37 | * @param ActivityProfileDocument $document The document to store
38 | */
39 | public function createOrReplaceDocument(ActivityProfileDocument $document);
40 |
41 | /**
42 | * Deletes a document stored for the given activity profile.
43 | *
44 | * @param ActivityProfile $profile The activity profile
45 | */
46 | public function deleteDocument(ActivityProfile $profile);
47 |
48 | /**
49 | * Returns the document for an activity profile.
50 | *
51 | * @param ActivityProfile $profile The activity profile to request the
52 | * document for
53 | *
54 | * @return ActivityProfileDocument The document
55 | */
56 | public function getDocument(ActivityProfile $profile);
57 | }
58 |
--------------------------------------------------------------------------------
/src/Api/AgentProfileApiClient.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Api;
13 |
14 | use Xabbuh\XApi\Client\Request\HandlerInterface;
15 | use Xabbuh\XApi\Serializer\ActorSerializerInterface;
16 | use Xabbuh\XApi\Serializer\DocumentDataSerializerInterface;
17 | use Xabbuh\XApi\Model\AgentProfile;
18 | use Xabbuh\XApi\Model\AgentProfileDocument;
19 |
20 | /**
21 | * Client to access the agent profile API of an xAPI based learning record
22 | * store.
23 | *
24 | * @author Christian Flothmann
25 | */
26 | final class AgentProfileApiClient extends DocumentApiClient implements AgentProfileApiClientInterface
27 | {
28 | /**
29 | * @var ActorSerializerInterface
30 | */
31 | private $actorSerializer;
32 |
33 | /**
34 | * @param HandlerInterface $requestHandler The HTTP request handler
35 | * @param string $version The xAPI version
36 | * @param DocumentDataSerializerInterface $documentDataSerializer The document data serializer
37 | * @param ActorSerializerInterface $actorSerializer The actor serializer
38 | */
39 | public function __construct(
40 | HandlerInterface $requestHandler,
41 | $version,
42 | DocumentDataSerializerInterface $documentDataSerializer,
43 | ActorSerializerInterface $actorSerializer
44 | ) {
45 | parent::__construct($requestHandler, $version, $documentDataSerializer);
46 |
47 | $this->actorSerializer = $actorSerializer;
48 | }
49 |
50 | /**
51 | * {@inheritDoc}
52 | */
53 | public function createOrUpdateDocument(AgentProfileDocument $document)
54 | {
55 | $profile = $document->getAgentProfile();
56 | $this->doStoreDocument('post', 'agents/profile', array(
57 | 'agent' => $this->actorSerializer->serializeActor($profile->getAgent()),
58 | 'profileId' => $profile->getProfileId(),
59 | ), $document);
60 | }
61 |
62 | /**
63 | * {@inheritDoc}
64 | */
65 | public function createOrReplaceDocument(AgentProfileDocument $document)
66 | {
67 | $profile = $document->getAgentProfile();
68 | $this->doStoreDocument('put', 'agents/profile', array(
69 | 'agent' => $this->actorSerializer->serializeActor($profile->getAgent()),
70 | 'profileId' => $profile->getProfileId(),
71 | ), $document);
72 | }
73 |
74 | /**
75 | * {@inheritDoc}
76 | */
77 | public function deleteDocument(AgentProfile $profile)
78 | {
79 | $this->doDeleteDocument('agents/profile', array(
80 | 'agent' => $this->actorSerializer->serializeActor($profile->getAgent()),
81 | 'profileId' => $profile->getProfileId(),
82 | ));
83 | }
84 |
85 | /**
86 | * {@inheritDoc}
87 | */
88 | public function getDocument(AgentProfile $profile)
89 | {
90 | /** @var \Xabbuh\XApi\Model\DocumentData $documentData */
91 | $documentData = $this->doGetDocument('agents/profile', array(
92 | 'agent' => $this->actorSerializer->serializeActor($profile->getAgent()),
93 | 'profileId' => $profile->getProfileId(),
94 | ));
95 |
96 | return new AgentProfileDocument($profile, $documentData);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Api/AgentProfileApiClientInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Api;
13 |
14 | use Xabbuh\XApi\Model\AgentProfile;
15 | use Xabbuh\XApi\Model\AgentProfileDocument;
16 |
17 | /**
18 | * Client to access the agent profile API of an xAPI based learning record
19 | * store.
20 | *
21 | * @author Christian Flothmann
22 | */
23 | interface AgentProfileApiClientInterface
24 | {
25 | /**
26 | * Stores a document for an agent profile. Updates an existing document for
27 | * this agent profile if one exists.
28 | *
29 | * @param AgentProfileDocument $document The document to store
30 | */
31 | public function createOrUpdateDocument(AgentProfileDocument $document);
32 |
33 | /**
34 | * Stores a document for an agent profile. Replaces any existing document
35 | * for this agent profile.
36 | *
37 | * @param AgentProfileDocument $document The document to store
38 | */
39 | public function createOrReplaceDocument(AgentProfileDocument $document);
40 |
41 | /**
42 | * Deletes a document stored for the given agent profile.
43 | *
44 | * @param AgentProfile $profile The agent profile
45 | */
46 | public function deleteDocument(AgentProfile $profile);
47 |
48 | /**
49 | * Returns the document for an agent profile.
50 | *
51 | * @param AgentProfile $profile The agent profile to request the document for
52 | *
53 | * @return AgentProfileDocument The document
54 | */
55 | public function getDocument(AgentProfile $profile);
56 | }
57 |
--------------------------------------------------------------------------------
/src/Api/DocumentApiClient.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Api;
13 |
14 | use Xabbuh\XApi\Client\Request\HandlerInterface;
15 | use Xabbuh\XApi\Model\Document;
16 | use Xabbuh\XApi\Model\DocumentData;
17 | use Xabbuh\XApi\Serializer\DocumentDataSerializerInterface;
18 |
19 | /**
20 | * Base class for the document API classes.
21 | *
22 | * @author Christian Flothmann
23 | */
24 | abstract class DocumentApiClient
25 | {
26 | private $requestHandler;
27 | private $version;
28 | private $documentDataSerializer;
29 |
30 | /**
31 | * @param HandlerInterface $requestHandler The HTTP request handler
32 | * @param string $version The xAPI version
33 | * @param DocumentDataSerializerInterface $documentDataSerializer The document data serializer
34 | */
35 | public function __construct(HandlerInterface $requestHandler, $version, DocumentDataSerializerInterface $documentDataSerializer)
36 | {
37 | $this->requestHandler = $requestHandler;
38 | $this->version = $version;
39 | $this->documentDataSerializer = $documentDataSerializer;
40 | }
41 |
42 | /**
43 | * Stores a document.
44 | *
45 | * @param string $method HTTP method to use
46 | * @param string $uri Endpoint URI
47 | * @param array $urlParameters URL parameters
48 | * @param Document $document The document to store
49 | */
50 | protected function doStoreDocument($method, $uri, $urlParameters, Document $document)
51 | {
52 | $request = $this->requestHandler->createRequest(
53 | $method,
54 | $uri,
55 | $urlParameters,
56 | $this->documentDataSerializer->serializeDocumentData($document->getData())
57 | );
58 | $this->requestHandler->executeRequest($request, array(204));
59 | }
60 |
61 | /**
62 | * Deletes a document.
63 | *
64 | * @param string $uri The endpoint URI
65 | * @param array $urlParameters The URL parameters
66 | */
67 | protected function doDeleteDocument($uri, array $urlParameters)
68 | {
69 | $request = $this->requestHandler->createRequest('delete', $uri, $urlParameters);
70 | $this->requestHandler->executeRequest($request, array(204));
71 | }
72 |
73 | /**
74 | * Returns a document.
75 | *
76 | * @param string $uri The endpoint URI
77 | * @param array $urlParameters The URL parameters
78 | *
79 | * @return Document The document
80 | */
81 | protected function doGetDocument($uri, array $urlParameters)
82 | {
83 | $request = $this->requestHandler->createRequest('get', $uri, $urlParameters);
84 | $response = $this->requestHandler->executeRequest($request, array(200));
85 | $document = $this->deserializeDocument((string) $response->getBody());
86 |
87 | return $document;
88 | }
89 |
90 | /**
91 | * Deserializes the data of a document.
92 | *
93 | * @param string $data The serialized document data
94 | *
95 | * @return DocumentData The parsed document data
96 | */
97 | protected function deserializeDocument($data)
98 | {
99 | return $this->documentDataSerializer->deserializeDocumentData($data);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Api/StateApiClient.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Api;
13 |
14 | use Xabbuh\XApi\Client\Request\HandlerInterface;
15 | use Xabbuh\XApi\Serializer\ActorSerializerInterface;
16 | use Xabbuh\XApi\Serializer\DocumentDataSerializerInterface;
17 | use Xabbuh\XApi\Model\StateDocument;
18 | use Xabbuh\XApi\Model\State;
19 |
20 | /**
21 | * Client to access the state API of an xAPI based learning record store.
22 | *
23 | * @author Christian Flothmann
24 | */
25 | final class StateApiClient extends DocumentApiClient implements StateApiClientInterface
26 | {
27 | /**
28 | * @var ActorSerializerInterface
29 | */
30 | private $actorSerializer;
31 |
32 | /**
33 | * @param HandlerInterface $requestHandler The HTTP request handler
34 | * @param string $version The xAPI version
35 | * @param DocumentDataSerializerInterface $documentDataSerializer The document data serializer
36 | * @param ActorSerializerInterface $actorSerializer The actor serializer
37 | */
38 | public function __construct(
39 | HandlerInterface $requestHandler,
40 | $version,
41 | DocumentDataSerializerInterface $documentDataSerializer,
42 | ActorSerializerInterface $actorSerializer
43 | ) {
44 | parent::__construct($requestHandler, $version, $documentDataSerializer);
45 |
46 | $this->actorSerializer = $actorSerializer;
47 | }
48 |
49 | /**
50 | * {@inheritDoc}
51 | */
52 | public function createOrUpdateDocument(StateDocument $document)
53 | {
54 | $this->doStoreStateDocument('post', $document);
55 | }
56 |
57 | /**
58 | * {@inheritDoc}
59 | */
60 | public function createOrReplaceDocument(StateDocument $document)
61 | {
62 | $this->doStoreStateDocument('put', $document);
63 | }
64 |
65 | /**
66 | * {@inheritDoc}
67 | */
68 | public function deleteDocument(State $state)
69 | {
70 | $this->doDeleteDocument('activities/state', array(
71 | 'activityId' => $state->getActivity()->getId()->getValue(),
72 | 'agent' => $this->actorSerializer->serializeActor($state->getActor()),
73 | 'stateId' => $state->getStateId(),
74 | ));
75 | }
76 |
77 | /**
78 | * {@inheritDoc}
79 | */
80 | public function getDocument(State $state)
81 | {
82 | /** @var \Xabbuh\XApi\Model\DocumentData $documentData */
83 | $documentData = $this->doGetDocument('activities/state', array(
84 | 'activityId' => $state->getActivity()->getId()->getValue(),
85 | 'agent' => $this->actorSerializer->serializeActor($state->getActor()),
86 | 'stateId' => $state->getStateId(),
87 | ));
88 |
89 | return new StateDocument($state, $documentData);
90 | }
91 |
92 | /**
93 | * Stores a state document.
94 | *
95 | * @param string $method HTTP method to use
96 | * @param StateDocument $document The document to store
97 | */
98 | private function doStoreStateDocument($method, StateDocument $document)
99 | {
100 | $state = $document->getState();
101 | $this->doStoreDocument(
102 | $method,
103 | 'activities/state',
104 | array(
105 | 'activityId' => $state->getActivity()->getId()->getValue(),
106 | 'agent' => $this->actorSerializer->serializeActor($state->getActor()),
107 | 'stateId' => $state->getStateId(),
108 | ),
109 | $document
110 | );
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Api/StateApiClientInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Api;
13 |
14 | use Xabbuh\XApi\Model\StateDocument;
15 | use Xabbuh\XApi\Model\State;
16 |
17 | /**
18 | * Client to access the state API of an xAPI based learning record store.
19 | *
20 | * @author Christian Flothmann
21 | */
22 | interface StateApiClientInterface
23 | {
24 | /**
25 | * Stores a document for a state. Updates an existing document for this
26 | * state if one exists.
27 | *
28 | * @param StateDocument $document The document to store
29 | */
30 | public function createOrUpdateDocument(StateDocument $document);
31 |
32 | /**
33 | * Stores a document for a state. Replaces any existing document for this
34 | * state.
35 | *
36 | * @param StateDocument $document The document to store
37 | */
38 | public function createOrReplaceDocument(StateDocument $document);
39 |
40 | /**
41 | * Deletes a document stored for the given state.
42 | *
43 | * @param State $state The state
44 | */
45 | public function deleteDocument(State $state);
46 |
47 | /**
48 | * Returns the document for a state.
49 | *
50 | * @param State $state The state to request the document for
51 | *
52 | * @return StateDocument The document
53 | */
54 | public function getDocument(State $state);
55 | }
56 |
--------------------------------------------------------------------------------
/src/Api/StatementsApiClient.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Api;
13 |
14 | use Xabbuh\XApi\Client\Http\MultipartStatementBody;
15 | use Xabbuh\XApi\Client\Request\HandlerInterface;
16 | use Xabbuh\XApi\Model\StatementId;
17 | use Xabbuh\XApi\Serializer\ActorSerializerInterface;
18 | use Xabbuh\XApi\Serializer\StatementResultSerializerInterface;
19 | use Xabbuh\XApi\Serializer\StatementSerializerInterface;
20 | use Xabbuh\XApi\Model\Actor;
21 | use Xabbuh\XApi\Model\Statement;
22 | use Xabbuh\XApi\Model\StatementResult;
23 | use Xabbuh\XApi\Model\StatementsFilter;
24 |
25 | /**
26 | * Client to access the statements API of an xAPI based learning record store.
27 | *
28 | * @author Christian Flothmann
29 | */
30 | final class StatementsApiClient implements StatementsApiClientInterface
31 | {
32 | private $requestHandler;
33 | private $version;
34 | private $statementSerializer;
35 | private $statementResultSerializer;
36 | private $actorSerializer;
37 |
38 | /**
39 | * @param HandlerInterface $requestHandler The HTTP request handler
40 | * @param string $version The xAPI version
41 | * @param StatementSerializerInterface $statementSerializer The statement serializer
42 | * @param StatementResultSerializerInterface $statementResultSerializer The statement result serializer
43 | * @param ActorSerializerInterface $actorSerializer The actor serializer
44 | */
45 | public function __construct(
46 | HandlerInterface $requestHandler,
47 | $version,
48 | StatementSerializerInterface $statementSerializer,
49 | StatementResultSerializerInterface $statementResultSerializer,
50 | ActorSerializerInterface $actorSerializer
51 | ) {
52 | $this->requestHandler = $requestHandler;
53 | $this->version = $version;
54 | $this->statementSerializer = $statementSerializer;
55 | $this->statementResultSerializer = $statementResultSerializer;
56 | $this->actorSerializer = $actorSerializer;
57 | }
58 |
59 | /**
60 | * {@inheritDoc}
61 | */
62 | public function storeStatement(Statement $statement)
63 | {
64 | if (null !== $statement->getId()) {
65 | return $this->doStoreStatements(
66 | $statement,
67 | 'put',
68 | array('statementId' => $statement->getId()->getValue()),
69 | 204
70 | );
71 | } else {
72 | return $this->doStoreStatements($statement);
73 | }
74 | }
75 |
76 | /**
77 | * {@inheritDoc}
78 | */
79 | public function storeStatements(array $statements)
80 | {
81 | // check that only Statements without ids will be sent to the LRS
82 | foreach ($statements as $statement) {
83 | /** @var Statement $statement */
84 |
85 | $isStatement = is_object($statement) && $statement instanceof Statement;
86 |
87 | if (!$isStatement || null !== $statement->getId()) {
88 | throw new \InvalidArgumentException('API can only handle statements without ids');
89 | }
90 | }
91 |
92 | return $this->doStoreStatements($statements);
93 | }
94 |
95 | /**
96 | * {@inheritDoc}
97 | */
98 | public function voidStatement(Statement $statement, Actor $actor)
99 | {
100 | return $this->storeStatement($statement->getVoidStatement($actor));
101 | }
102 |
103 | /**
104 | * {@inheritDoc}
105 | */
106 | public function getStatement(StatementId $statementId, $attachments = true)
107 | {
108 | return $this->doGetStatements('statements', array(
109 | 'statementId' => $statementId->getValue(),
110 | 'attachments' => $attachments ? 'true' : 'false',
111 | ));
112 | }
113 |
114 | /**
115 | * {@inheritDoc}
116 | */
117 | public function getVoidedStatement(StatementId $statementId, $attachments = true)
118 | {
119 | return $this->doGetStatements('statements', array(
120 | 'voidedStatementId' => $statementId->getValue(),
121 | 'attachments' => $attachments ? 'true' : 'false',
122 | ));
123 | }
124 |
125 | /**
126 | * {@inheritDoc}
127 | */
128 | public function getStatements(StatementsFilter $filter = null, $attachments = true)
129 | {
130 | $urlParameters = array();
131 |
132 | if (null !== $filter) {
133 | $urlParameters = $filter->getFilter();
134 | }
135 |
136 | // the Agent must be JSON encoded
137 | if (isset($urlParameters['agent'])) {
138 | $urlParameters['agent'] = $this->actorSerializer->serializeActor($urlParameters['agent']);
139 | }
140 |
141 | return $this->doGetStatements('statements', $urlParameters);
142 | }
143 |
144 | /**
145 | * {@inheritDoc}
146 | */
147 | public function getNextStatements(StatementResult $statementResult)
148 | {
149 | return $this->doGetStatements($statementResult->getMoreUrlPath()->getValue());
150 | }
151 |
152 | /**
153 | * @param Statement|Statement[] $statements
154 | * @param string $method
155 | * @param string[] $parameters
156 | * @param int $validStatusCode
157 | *
158 | * @return Statement|Statement[] The created statement(s)
159 | */
160 | private function doStoreStatements($statements, $method = 'post', $parameters = array(), $validStatusCode = 200)
161 | {
162 | $attachments = array();
163 |
164 | if (is_array($statements)) {
165 | foreach ($statements as $statement) {
166 | if (null !== $statement->getAttachments()) {
167 | foreach ($statement->getAttachments() as $attachment) {
168 | if ($attachment->getContent()) {
169 | $attachments[] = $attachment;
170 | }
171 | }
172 | }
173 | }
174 |
175 | $serializedStatements = $this->statementSerializer->serializeStatements($statements);
176 | } else {
177 | if (null !== $statements->getAttachments()) {
178 | foreach ($statements->getAttachments() as $attachment) {
179 | if ($attachment->getContent()) {
180 | $attachments[] = $attachment;
181 | }
182 | }
183 | }
184 |
185 | $serializedStatements = $this->statementSerializer->serializeStatement($statements);
186 | }
187 |
188 | $headers = array();
189 |
190 | if (!empty($attachments)) {
191 | $builder = new MultipartStatementBody($serializedStatements, $attachments);
192 | $headers = array(
193 | 'Content-Type' => 'multipart/mixed; boundary='.$builder->getBoundary(),
194 | );
195 | $body = $builder->build();
196 | } else {
197 | $body = $serializedStatements;
198 | }
199 |
200 | $request = $this->requestHandler->createRequest(
201 | $method,
202 | 'statements',
203 | $parameters,
204 | $body,
205 | $headers
206 | );
207 | $response = $this->requestHandler->executeRequest($request, array($validStatusCode));
208 | $statementIds = json_decode((string) $response->getBody());
209 |
210 | if (is_array($statements)) {
211 | /** @var Statement[] $statements */
212 | $createdStatements = array();
213 |
214 | foreach ($statements as $index => $statement) {
215 | $createdStatements[] = $statement->withId(StatementId::fromString($statementIds[$index]));
216 | }
217 |
218 | return $createdStatements;
219 | } else {
220 | /** @var Statement $statements */
221 |
222 | if (200 === $validStatusCode) {
223 | return $statements->withId(StatementId::fromString($statementIds[0]));
224 | } else {
225 | return $statements;
226 | }
227 | }
228 | }
229 |
230 | /**
231 | * Fetch one or more Statements.
232 | *
233 | * @param string $url URL to request
234 | * @param array $urlParameters URL parameters
235 | *
236 | * @return Statement|StatementResult
237 | */
238 | private function doGetStatements($url, array $urlParameters = array())
239 | {
240 | $request = $this->requestHandler->createRequest('get', $url, $urlParameters);
241 | $response = $this->requestHandler->executeRequest($request, array(200));
242 |
243 | $contentType = $response->getHeader('Content-Type')[0];
244 | $body = (string) $response->getBody();
245 | $attachments = array();
246 |
247 | if (false !== strpos($contentType, 'application/json')) {
248 | $serializedStatement = $body;
249 | } else {
250 | $boundary = substr($contentType, strpos($contentType, '=') + 1);
251 | $parts = $this->parseMultipartResponseBody($body, $boundary);
252 | $serializedStatement = $parts[0]['content'];
253 |
254 | unset($parts[0]);
255 |
256 | foreach ($parts as $part) {
257 | $attachments[$part['headers']['X-Experience-API-Hash'][0]] = array(
258 | 'type' => $part['headers']['Content-Type'][0],
259 | 'content' => $part['content'],
260 | );
261 | }
262 | }
263 |
264 | if (isset($urlParameters['statementId']) || isset($urlParameters['voidedStatementId'])) {
265 | return $this->statementSerializer->deserializeStatement($serializedStatement, $attachments);
266 | } else {
267 | return $this->statementResultSerializer->deserializeStatementResult($serializedStatement, $attachments);
268 | }
269 | }
270 |
271 | private function parseMultipartResponseBody($body, $boundary)
272 | {
273 | $parts = array();
274 | $lines = explode("\r\n", $body);
275 | $currentPart = null;
276 | $isHeaderLine = true;
277 |
278 | foreach ($lines as $line) {
279 | if (false !== strpos($line, '--'.$boundary)) {
280 | if (null !== $currentPart) {
281 | $parts[] = $currentPart;
282 | }
283 |
284 | $currentPart = array(
285 | 'headers' => array(),
286 | 'content' => '',
287 | );
288 | $isBoundaryLine = true;
289 | $isHeaderLine = true;
290 | } else {
291 | $isBoundaryLine = false;
292 | }
293 |
294 | if ('' === $line) {
295 | $isHeaderLine = false;
296 | continue;
297 | }
298 |
299 | if (!$isBoundaryLine && !$isHeaderLine) {
300 | $currentPart['content'] .= $line;
301 | } elseif (!$isBoundaryLine && $isHeaderLine) {
302 | list($name, $value) = explode(':', $line, 2);
303 | $currentPart['headers'][$name][] = $value;
304 | }
305 | }
306 |
307 | return $parts;
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/src/Api/StatementsApiClientInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Api;
13 |
14 | use Xabbuh\XApi\Common\Exception\ConflictException;
15 | use Xabbuh\XApi\Common\Exception\NotFoundException;
16 | use Xabbuh\XApi\Common\Exception\XApiException;
17 | use Xabbuh\XApi\Model\Actor;
18 | use Xabbuh\XApi\Model\Statement;
19 | use Xabbuh\XApi\Model\StatementId;
20 | use Xabbuh\XApi\Model\StatementResult;
21 | use Xabbuh\XApi\Model\StatementsFilter;
22 |
23 | /**
24 | * Client to access the statements API of an xAPI based learning record store.
25 | *
26 | * @author Christian Flothmann
27 | */
28 | interface StatementsApiClientInterface
29 | {
30 | /**
31 | * Stores a single {@link Statement}.
32 | *
33 | * @param Statement $statement The Statement to store
34 | *
35 | * @return Statement The Statement as it has been stored in the remote LRS,
36 | * this is not necessarily the same object that was
37 | * passed to storeStatement()
38 | *
39 | * @throws ConflictException if a Statement with the given id already exists
40 | * and the given Statement does not match the
41 | * stored Statement
42 | * @throws XApiException for all other xAPI related problems
43 | */
44 | public function storeStatement(Statement $statement);
45 |
46 | /**
47 | * Stores a collection of {@link Statement Statements}.
48 | *
49 | * @param Statement[] $statements The statements to store
50 | *
51 | * @return Statement[] The stored Statements
52 | *
53 | * @throws \InvalidArgumentException if a given object is no Statement or
54 | * if one of the Statements has an id
55 | * @throws XApiException for all other xAPI related problems
56 | */
57 | public function storeStatements(array $statements);
58 |
59 | /**
60 | * Marks a {@link Statement} as voided.
61 | *
62 | * @param Statement $statement The Statement to void
63 | * @param Actor $actor The Actor voiding the given Statement
64 | *
65 | * @return Statement The Statement sent to the remote LRS to void the
66 | * given Statement
67 | *
68 | * @throws XApiException for all other xAPI related problems
69 | */
70 | public function voidStatement(Statement $statement, Actor $actor);
71 |
72 | /**
73 | * Retrieves a single {@link Statement Statement}.
74 | *
75 | * @param StatementId $statementId The Statement id
76 | * @param bool $attachments Whether or not to request raw attachment data
77 | *
78 | * @return Statement The Statement
79 | *
80 | * @throws NotFoundException if no statement with the given id could be found
81 | * @throws XApiException for all other xAPI related problems
82 | */
83 | public function getStatement(StatementId $statementId, $attachments = true);
84 |
85 | /**
86 | * Retrieves a voided {@link Statement Statement}.
87 | *
88 | * @param StatementId $statementId The id of the voided Statement
89 | * @param bool $attachments Whether or not to request raw attachment data
90 | *
91 | * @return Statement The voided Statement
92 | *
93 | * @throws NotFoundException if no statement with the given id could be found
94 | * @throws XApiException for all other xAPI related problems
95 | */
96 | public function getVoidedStatement(StatementId $statementId, $attachments = true);
97 |
98 | /**
99 | * Retrieves a collection of {@link Statement Statements}.
100 | *
101 | * @param StatementsFilter $filter Optional Statements filter
102 | * @param bool $attachments Whether or not to request raw attachment data
103 | *
104 | * @return StatementResult The {@link StatementResult}
105 | *
106 | * @throws XApiException in case of any problems related to the xAPI
107 | */
108 | public function getStatements(StatementsFilter $filter = null, $attachments = true);
109 |
110 | /**
111 | * Returns the next {@link Statement Statements} for a limited Statement
112 | * result.
113 | *
114 | * @param StatementResult $statementResult The former StatementResult
115 | *
116 | * @return StatementResult The {@link StatementResult}
117 | *
118 | * @throws XApiException in case of any problems related to the xAPI
119 | */
120 | public function getNextStatements(StatementResult $statementResult);
121 | }
122 |
--------------------------------------------------------------------------------
/src/Http/MultipartStatementBody.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Http;
13 |
14 | use Xabbuh\XApi\Model\Attachment;
15 |
16 | /**
17 | * HTTP message body containing serialized statements and their attachments.
18 | *
19 | * @author Christian Flothmann
20 | */
21 | final class MultipartStatementBody
22 | {
23 | private $boundary;
24 | private $serializedStatements;
25 | private $attachments;
26 |
27 | /**
28 | * @param string $serializedStatements The JSON encoded statement(s)
29 | * @param Attachment[] $attachments The statement attachments that include not only a file URL
30 | */
31 | public function __construct($serializedStatements, array $attachments)
32 | {
33 | $this->boundary = uniqid();
34 | $this->serializedStatements = $serializedStatements;
35 | $this->attachments = $attachments;
36 | }
37 |
38 | public function getBoundary()
39 | {
40 | return $this->boundary;
41 | }
42 |
43 | public function build()
44 | {
45 | $body = '--'.$this->boundary."\r\n";
46 | $body .= "Content-Type: application/json\r\n";
47 | $body .= 'Content-Length: '.strlen($this->serializedStatements)."\r\n";
48 | $body .= "\r\n";
49 | $body .= $this->serializedStatements."\r\n";
50 |
51 | foreach ($this->attachments as $attachment) {
52 | $body .= '--'.$this->boundary."\r\n";
53 | $body .= 'Content-Type: '.$attachment->getContentType()."\r\n";
54 | $body .= "Content-Transfer-Encoding: binary\r\n";
55 | $body .= 'Content-Length: '.$attachment->getLength()."\r\n";
56 | $body .= 'X-Experience-API-Hash: '.$attachment->getSha2()."\r\n";
57 | $body .= "\r\n";
58 | $body .= $attachment->getContent()."\r\n";
59 | }
60 |
61 | $body .= '--'.$this->boundary.'--'."\r\n";
62 |
63 | return $body;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Request/Handler.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Request;
13 |
14 | use Http\Client\Exception;
15 | use Http\Client\HttpClient;
16 | use Http\Message\RequestFactory;
17 | use Psr\Http\Message\RequestInterface;
18 | use Xabbuh\XApi\Common\Exception\AccessDeniedException;
19 | use Xabbuh\XApi\Common\Exception\ConflictException;
20 | use Xabbuh\XApi\Common\Exception\NotFoundException;
21 | use Xabbuh\XApi\Common\Exception\XApiException;
22 |
23 | /**
24 | * Prepares and executes xAPI HTTP requests.
25 | *
26 | * @author Christian Flothmann
27 | */
28 | final class Handler implements HandlerInterface
29 | {
30 | private $httpClient;
31 | private $requestFactory;
32 | private $baseUri;
33 | private $version;
34 |
35 | /**
36 | * @param HttpClient $httpClient The HTTP client sending requests to the remote LRS
37 | * @param RequestFactory $requestFactory The factory used to create PSR-7 HTTP requests
38 | * @param string $baseUri The APIs base URI (all end points will be created relatively to this URI)
39 | * @param string $version The xAPI version
40 | */
41 | public function __construct(HttpClient $httpClient, RequestFactory $requestFactory, $baseUri, $version)
42 | {
43 | $this->httpClient = $httpClient;
44 | $this->requestFactory = $requestFactory;
45 | $this->baseUri = $baseUri;
46 | $this->version = $version;
47 | }
48 |
49 | /**
50 | * {@inheritDoc}
51 | */
52 | public function createRequest($method, $uri, array $urlParameters = array(), $body = null, array $headers = array())
53 | {
54 | if (!in_array(strtoupper($method), array('GET', 'POST', 'PUT', 'DELETE'))) {
55 | throw new \InvalidArgumentException(sprintf('"%s" is no valid HTTP method (expected one of [GET, POST, PUT, DELETE]) in an xAPI context.', $method));
56 | }
57 |
58 | $uri = rtrim($this->baseUri, '/').'/'.ltrim($uri, '/');
59 |
60 | if (count($urlParameters) > 0) {
61 | $uri .= '?'.http_build_query($urlParameters);
62 | }
63 |
64 | if (!isset($headers['X-Experience-API-Version'])) {
65 | $headers['X-Experience-API-Version'] = $this->version;
66 | }
67 |
68 | if (!isset($headers['Content-Type'])) {
69 | $headers['Content-Type'] = 'application/json';
70 | }
71 |
72 | return $this->requestFactory->createRequest(strtoupper($method), $uri, $headers, $body);
73 | }
74 |
75 | /**
76 | * {@inheritDoc}
77 | */
78 | public function executeRequest(RequestInterface $request, array $validStatusCodes)
79 | {
80 | try {
81 | $response = $this->httpClient->sendRequest($request);
82 | } catch (Exception $e) {
83 | throw new XApiException($e->getMessage(), $e->getCode(), $e);
84 | }
85 |
86 | // catch some common errors
87 | if (in_array($response->getStatusCode(), array(401, 403))) {
88 | throw new AccessDeniedException(
89 | (string) $response->getBody(),
90 | $response->getStatusCode()
91 | );
92 | } elseif (404 === $response->getStatusCode()) {
93 | throw new NotFoundException((string) $response->getBody());
94 | } elseif (409 === $response->getStatusCode()) {
95 | throw new ConflictException((string) $response->getBody());
96 | }
97 |
98 | if (!in_array($response->getStatusCode(), $validStatusCodes)) {
99 | throw new XApiException((string) $response->getBody(), $response->getStatusCode());
100 | }
101 |
102 | return $response;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/Request/HandlerInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Request;
13 |
14 | use Psr\Http\Message\RequestInterface;
15 | use Psr\Http\Message\ResponseInterface;
16 | use Psr\Http\Message\StreamInterface;
17 | use Xabbuh\XApi\Common\Exception\XApiException;
18 |
19 | /**
20 | * Prepare and execute xAPI HTTP requests.
21 | *
22 | * @author Christian Flothmann
23 | */
24 | interface HandlerInterface
25 | {
26 | /**
27 | * @param string $method The HTTP method
28 | * @param string $uri The URI to send the request to
29 | * @param array $urlParameters Optional url parameters
30 | * @param string $body An optional request body
31 | * @param array $headers Optional additional HTTP headers
32 | *
33 | * @return RequestInterface The request
34 | *
35 | * @throws \InvalidArgumentException when no valid HTTP method is given
36 | */
37 | public function createRequest($method, $uri, array $urlParameters = array(), $body = null, array $headers = array());
38 |
39 | /**
40 | * Performs the given HTTP request.
41 | *
42 | * @param RequestInterface $request The HTTP request to perform
43 | * @param int[] $validStatusCodes A list of HTTP status codes
44 | * the calling method is able to
45 | * handle
46 | *
47 | * @return ResponseInterface The remote server's response
48 | *
49 | * @throws XApiException when the request fails
50 | */
51 | public function executeRequest(RequestInterface $request, array $validStatusCodes);
52 | }
53 |
--------------------------------------------------------------------------------
/src/XApiClient.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client;
13 |
14 | use Xabbuh\XApi\Client\Api\ActivityProfileApiClient;
15 | use Xabbuh\XApi\Client\Api\AgentProfileApiClient;
16 | use Xabbuh\XApi\Client\Api\ApiClient;
17 | use Xabbuh\XApi\Client\Api\StateApiClient;
18 | use Xabbuh\XApi\Client\Api\StatementsApiClient;
19 | use Xabbuh\XApi\Client\Request\HandlerInterface;
20 | use Xabbuh\XApi\Serializer\SerializerRegistryInterface;
21 |
22 | /**
23 | * An Experience API client.
24 | *
25 | * @author Christian Flothmann
26 | */
27 | final class XApiClient implements XApiClientInterface
28 | {
29 | /**
30 | * @var SerializerRegistryInterface
31 | */
32 | private $serializerRegistry;
33 |
34 | /**
35 | * @param HandlerInterface $requestHandler The HTTP request handler
36 | * @param SerializerRegistryInterface $serializerRegistry The serializer registry
37 | * @param string $version The xAPI version
38 | */
39 | public function __construct(HandlerInterface $requestHandler, SerializerRegistryInterface $serializerRegistry, $version)
40 | {
41 | $this->requestHandler = $requestHandler;
42 | $this->serializerRegistry = $serializerRegistry;
43 | $this->version = $version;
44 | }
45 |
46 | /**
47 | * {@inheritDoc}
48 | */
49 | public function getStatementsApiClient()
50 | {
51 | return new StatementsApiClient(
52 | $this->requestHandler,
53 | $this->version,
54 | $this->serializerRegistry->getStatementSerializer(),
55 | $this->serializerRegistry->getStatementResultSerializer(),
56 | $this->serializerRegistry->getActorSerializer()
57 | );
58 | }
59 |
60 | /**
61 | * {@inheritDoc}
62 | */
63 | public function getStateApiClient()
64 | {
65 | return new StateApiClient(
66 | $this->requestHandler,
67 | $this->version,
68 | $this->serializerRegistry->getDocumentDataSerializer(),
69 | $this->serializerRegistry->getActorSerializer()
70 | );
71 | }
72 |
73 | /**
74 | * {@inheritDoc}
75 | */
76 | public function getActivityProfileApiClient()
77 | {
78 | return new ActivityProfileApiClient(
79 | $this->requestHandler,
80 | $this->version,
81 | $this->serializerRegistry->getDocumentDataSerializer()
82 | );
83 | }
84 |
85 | /**
86 | * {@inheritDoc}
87 | */
88 | public function getAgentProfileApiClient()
89 | {
90 | return new AgentProfileApiClient(
91 | $this->requestHandler,
92 | $this->version,
93 | $this->serializerRegistry->getDocumentDataSerializer(),
94 | $this->serializerRegistry->getActorSerializer()
95 | );
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/XApiClientBuilder.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client;
13 |
14 | use ApiClients\Tools\Psr7\Oauth1\Definition\AccessToken;
15 | use ApiClients\Tools\Psr7\Oauth1\Definition\ConsumerKey;
16 | use ApiClients\Tools\Psr7\Oauth1\Definition\ConsumerSecret;
17 | use ApiClients\Tools\Psr7\Oauth1\Definition\TokenSecret;
18 | use ApiClients\Tools\Psr7\Oauth1\RequestSigning\RequestSigner;
19 | use Http\Client\Common\Plugin\AuthenticationPlugin;
20 | use Http\Client\Common\PluginClient;
21 | use Http\Client\HttpClient;
22 | use Http\Discovery\HttpClientDiscovery;
23 | use Http\Discovery\MessageFactoryDiscovery;
24 | use Http\Message\Authentication\BasicAuth;
25 | use Http\Message\RequestFactory;
26 | use Xabbuh\Http\Authentication\OAuth1;
27 | use Xabbuh\XApi\Client\Request\Handler;
28 | use Xabbuh\XApi\Serializer\SerializerFactoryInterface;
29 | use Xabbuh\XApi\Serializer\SerializerRegistry;
30 | use Xabbuh\XApi\Serializer\Symfony\SerializerFactory;
31 |
32 | /**
33 | * xAPI client builder.
34 | *
35 | * @author Christian Flothmann
36 | */
37 | final class XApiClientBuilder implements XApiClientBuilderInterface
38 | {
39 | private $serializerFactory;
40 |
41 | /**
42 | * @var HttpClient|null
43 | */
44 | private $httpClient;
45 |
46 | /**
47 | * @var RequestFactory|null
48 | */
49 | private $requestFactory;
50 |
51 | private $baseUrl;
52 | private $version;
53 | private $username;
54 | private $password;
55 | private $consumerKey;
56 | private $consumerSecret;
57 | private $accessToken;
58 | private $tokenSecret;
59 |
60 | public function __construct(SerializerFactoryInterface $serializerFactory = null)
61 | {
62 | $this->serializerFactory = $serializerFactory ?: new SerializerFactory();
63 | }
64 |
65 | /**
66 | * {@inheritdoc}
67 | */
68 | public function setHttpClient(HttpClient $httpClient)
69 | {
70 | $this->httpClient = $httpClient;
71 |
72 | return $this;
73 | }
74 |
75 | /**
76 | * {@inheritdoc}
77 | */
78 | public function setRequestFactory(RequestFactory $requestFactory)
79 | {
80 | $this->requestFactory = $requestFactory;
81 |
82 | return $this;
83 | }
84 |
85 | /**
86 | * {@inheritDoc}
87 | */
88 | public function setBaseUrl($baseUrl)
89 | {
90 | $this->baseUrl = $baseUrl;
91 |
92 | return $this;
93 | }
94 |
95 | /**
96 | * {@inheritDoc}
97 | */
98 | public function setVersion($version)
99 | {
100 | $this->version = $version;
101 |
102 | return $this;
103 | }
104 |
105 | /**
106 | * {@inheritDoc}
107 | */
108 | public function setAuth($username, $password)
109 | {
110 | $this->username = $username;
111 | $this->password = $password;
112 |
113 | return $this;
114 | }
115 |
116 | /**
117 | * {@inheritDoc}
118 | */
119 | public function setOAuthCredentials($consumerKey, $consumerSecret, $token, $tokenSecret)
120 | {
121 | $this->consumerKey = $consumerKey;
122 | $this->consumerSecret = $consumerSecret;
123 | $this->accessToken = $token;
124 | $this->tokenSecret = $tokenSecret;
125 |
126 | return $this;
127 | }
128 |
129 | /**
130 | * {@inheritDoc}
131 | */
132 | public function build()
133 | {
134 | if (null === $this->httpClient && class_exists(HttpClientDiscovery::class)) {
135 | try {
136 | $this->httpClient = HttpClientDiscovery::find();
137 | } catch (\Exception $e) {
138 | }
139 | }
140 |
141 | if (null === $httpClient = $this->httpClient) {
142 | throw new \LogicException('No HTTP client was configured.');
143 | }
144 |
145 | if (null === $this->requestFactory && class_exists(MessageFactoryDiscovery::class)) {
146 | try {
147 | $this->requestFactory = MessageFactoryDiscovery::find();
148 | } catch (\Exception $e) {
149 | }
150 | }
151 |
152 | if (null === $this->requestFactory) {
153 | throw new \LogicException('No request factory was configured.');
154 | }
155 |
156 | if (null === $this->baseUrl) {
157 | throw new \LogicException('Base URI value was not configured.');
158 | }
159 |
160 | $serializerRegistry = new SerializerRegistry();
161 | $serializerRegistry->setStatementSerializer($this->serializerFactory->createStatementSerializer());
162 | $serializerRegistry->setStatementResultSerializer($this->serializerFactory->createStatementResultSerializer());
163 | $serializerRegistry->setActorSerializer($this->serializerFactory->createActorSerializer());
164 | $serializerRegistry->setDocumentDataSerializer($this->serializerFactory->createDocumentDataSerializer());
165 |
166 | $plugins = array();
167 |
168 | if (null !== $this->username && null !== $this->password) {
169 | $plugins[] = new AuthenticationPlugin(new BasicAuth($this->username, $this->password));
170 | }
171 |
172 | if (null !== $this->consumerKey && null !== $this->consumerSecret && null !== $this->accessToken && null !== $this->tokenSecret) {
173 | if (!class_exists(OAuth1::class)) {
174 | throw new \LogicException('The "xabbuh/oauth1-authentication package is needed to use OAuth1 authorization.');
175 | }
176 |
177 | $requestSigner = new RequestSigner(new ConsumerKey($this->consumerKey), new ConsumerSecret($this->consumerSecret));
178 | $oauth = new OAuth1($requestSigner, new AccessToken($this->accessToken), new TokenSecret($this->tokenSecret));
179 | $plugins[] = new AuthenticationPlugin($oauth);
180 | }
181 |
182 | if (!empty($plugins)) {
183 | $httpClient = new PluginClient($httpClient, $plugins);
184 | }
185 |
186 | $version = null === $this->version ? '1.0.3' : $this->version;
187 | $requestHandler = new Handler($httpClient, $this->requestFactory, $this->baseUrl, $version);
188 |
189 | return new XApiClient($requestHandler, $serializerRegistry, $this->version);
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/XApiClientBuilderInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client;
13 |
14 | use Http\Client\HttpClient;
15 | use Http\Message\RequestFactory;
16 |
17 | /**
18 | * xAPI client builder.
19 | *
20 | * @author Christian Flothmann
21 | */
22 | interface XApiClientBuilderInterface
23 | {
24 | /**
25 | * Sets the HTTP client implementation that will be used to issue HTTP requests.
26 | *
27 | * @param HttpClient $httpClient The HTTP client implementation
28 | *
29 | * @return XApiClientBuilderInterface The builder
30 | */
31 | public function setHttpClient(HttpClient $httpClient);
32 |
33 | /**
34 | * Sets the requests factory which creates requests that are then handled by the HTTP client.
35 | *
36 | * @param RequestFactory $requestFactory The request factory
37 | *
38 | * @return XApiClientBuilderInterface The builder
39 | */
40 | public function setRequestFactory(RequestFactory $requestFactory);
41 |
42 | /**
43 | * Sets the LRS base URL.
44 | *
45 | * @param string $baseUrl The base url
46 | *
47 | * @return XApiClientBuilderInterface The builder
48 | */
49 | public function setBaseUrl($baseUrl);
50 |
51 | /**
52 | * Sets the xAPI version.
53 | *
54 | * @param string $version The version to use
55 | *
56 | * @return XApiClientBuilderInterface The builder
57 | */
58 | public function setVersion($version);
59 |
60 | /**
61 | * Sets HTTP authentication credentials.
62 | *
63 | * @param string $username The username
64 | * @param string $password The password
65 | *
66 | * @return XApiClientBuilderInterface The builder
67 | */
68 | public function setAuth($username, $password);
69 |
70 | /**
71 | * Sets OAuth credentials.
72 | *
73 | * @param string $consumerKey The consumer key
74 | * @param string $consumerSecret The consumer secret
75 | * @param string $token The token
76 | * @param string $tokenSecret The secret token
77 | *
78 | * @return XApiClientBuilderInterface The builder
79 | */
80 | public function setOAuthCredentials($consumerKey, $consumerSecret, $token, $tokenSecret);
81 |
82 | /**
83 | * Builds the xAPI client.
84 | *
85 | * @return XApiClientInterface The xAPI client
86 | *
87 | * @throws \LogicException if no base URI was configured
88 | */
89 | public function build();
90 | }
91 |
--------------------------------------------------------------------------------
/src/XApiClientInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client;
13 |
14 | /**
15 | * An Experience API client.
16 | *
17 | * @author Christian Flothmann
18 | */
19 | interface XApiClientInterface
20 | {
21 | /**
22 | * Returns an API client to access the statements API of an xAPI based LRS.
23 | *
24 | * @return \Xabbuh\XApi\Client\Api\StatementsApiClientInterface The API client
25 | */
26 | public function getStatementsApiClient();
27 |
28 | /**
29 | * Returns an API client to access the state API of an xAPI based LRS.
30 | *
31 | * @return \Xabbuh\XApi\Client\Api\StateApiClientInterface The API client
32 | */
33 | public function getStateApiClient();
34 |
35 | /**
36 | * Returns an API client to access the activity profile API of an xAPI based
37 | * LRS.
38 | *
39 | * @return \Xabbuh\XApi\Client\Api\ActivityProfileApiClientInterface The API client
40 | */
41 | public function getActivityProfileApiClient();
42 |
43 | /**
44 | * Returns an API client to access the agent profile API of an xAPI based
45 | * LRS.
46 | *
47 | * @return \Xabbuh\XApi\Client\Api\AgentProfileApiClientInterface The API client
48 | */
49 | public function getAgentProfileApiClient();
50 | }
51 |
--------------------------------------------------------------------------------
/tests/Api/ActivityProfileApiClientTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Tests\Api;
13 |
14 | use Xabbuh\XApi\Client\Api\ActivityProfileApiClient;
15 | use Xabbuh\XApi\DataFixtures\DocumentFixtures;
16 | use Xabbuh\XApi\Model\Activity;
17 | use Xabbuh\XApi\Model\ActivityProfile;
18 | use Xabbuh\XApi\Model\ActivityProfileDocument;
19 | use Xabbuh\XApi\Model\IRI;
20 | use Xabbuh\XApi\Serializer\Symfony\DocumentDataSerializer;
21 |
22 | /**
23 | * @author Christian Flothmann
24 | */
25 | class ActivityProfileApiClientTest extends ApiClientTest
26 | {
27 | /**
28 | * @var ActivityProfileApiClient
29 | */
30 | private $client;
31 |
32 | protected function setUp(): void
33 | {
34 | parent::setUp();
35 | $this->client = new ActivityProfileApiClient(
36 | $this->requestHandler,
37 | '1.0.1',
38 | new DocumentDataSerializer($this->serializer)
39 | );
40 | }
41 |
42 | public function testCreateOrUpdateDocument()
43 | {
44 | $document = DocumentFixtures::getActivityProfileDocument();
45 |
46 | $this->validateStoreApiCall(
47 | 'post',
48 | 'activities/profile',
49 | array(
50 | 'activityId' => 'activity-id',
51 | 'profileId' => 'profile-id',
52 | ),
53 | 204,
54 | '',
55 | $document->getData()
56 | );
57 |
58 | $this->client->createOrUpdateDocument($document);
59 | }
60 |
61 | public function testCreateOrReplaceDocument()
62 | {
63 | $document = DocumentFixtures::getActivityProfileDocument();
64 |
65 | $this->validateStoreApiCall(
66 | 'put',
67 | 'activities/profile',
68 | array(
69 | 'activityId' => 'activity-id',
70 | 'profileId' => 'profile-id',
71 | ),
72 | 204,
73 | '',
74 | $document->getData()
75 | );
76 |
77 | $this->client->createOrReplaceDocument($document);
78 | }
79 |
80 | public function testDeleteDocument()
81 | {
82 | $activityProfile = $this->createActivityProfile();
83 |
84 | $this->validateRequest(
85 | 'delete',
86 | 'activities/profile',
87 | array(
88 | 'activityId' => 'activity-id',
89 | 'profileId' => 'profile-id',
90 | ),
91 | ''
92 | );
93 | $this->validateSerializer(array());
94 |
95 | $this->client->deleteDocument($activityProfile);
96 | }
97 |
98 | public function testGetDocument()
99 | {
100 | $document = DocumentFixtures::getActivityProfileDocument();
101 | $activityProfile = $document->getActivityProfile();
102 |
103 | $this->validateRetrieveApiCall(
104 | 'get',
105 | 'activities/profile',
106 | array(
107 | 'activityId' => 'activity-id',
108 | 'profileId' => 'profile-id',
109 | ),
110 | 200,
111 | 'DocumentData',
112 | $document->getData()
113 | );
114 |
115 | $document = $this->client->getDocument($activityProfile);
116 |
117 | $this->assertInstanceOf(ActivityProfileDocument::class, $document);
118 | $this->assertEquals($activityProfile, $document->getActivityProfile());
119 | }
120 |
121 | private function createActivityProfile()
122 | {
123 | $activity = new Activity(IRI::fromString('activity-id'));
124 | $activityProfile = new ActivityProfile('profile-id', $activity);
125 |
126 | return $activityProfile;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/tests/Api/AgentProfileApiClientTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Tests\Api;
13 |
14 | use Xabbuh\XApi\Client\Api\AgentProfileApiClient;
15 | use Xabbuh\XApi\DataFixtures\DocumentFixtures;
16 | use Xabbuh\XApi\Model\Agent;
17 | use Xabbuh\XApi\Model\AgentProfile;
18 | use Xabbuh\XApi\Model\AgentProfileDocument;
19 | use Xabbuh\XApi\Model\InverseFunctionalIdentifier;
20 | use Xabbuh\XApi\Model\IRI;
21 | use Xabbuh\XApi\Serializer\Symfony\ActorSerializer;
22 | use Xabbuh\XApi\Serializer\Symfony\DocumentDataSerializer;
23 |
24 | /**
25 | * @author Christian Flothmann
26 | */
27 | class AgentProfileApiClientTest extends ApiClientTest
28 | {
29 | /**
30 | * @var AgentProfileApiClient
31 | */
32 | private $client;
33 |
34 | protected function setUp(): void
35 | {
36 | parent::setUp();
37 | $this->client = new AgentProfileApiClient(
38 | $this->requestHandler,
39 | '1.0.1',
40 | new DocumentDataSerializer($this->serializer),
41 | new ActorSerializer($this->serializer)
42 | );
43 | }
44 |
45 | public function testCreateOrUpdateDocument()
46 | {
47 | $document = DocumentFixtures::getAgentProfileDocument();
48 | $profile = $document->getAgentProfile();
49 |
50 | $this->validateStoreApiCall(
51 | 'post',
52 | 'agents/profile',
53 | array(
54 | 'agent' => 'agent-as-json',
55 | 'profileId' => 'profile-id',
56 | ),
57 | 204,
58 | '',
59 | $document->getData(),
60 | array(array('data' => $profile->getAgent(), 'result' => 'agent-as-json'))
61 | );
62 |
63 | $this->client->createOrUpdateDocument($document);
64 | }
65 |
66 | public function testCreateOrReplaceDocument()
67 | {
68 | $document = DocumentFixtures::getAgentProfileDocument();
69 | $profile = $document->getAgentProfile();
70 |
71 | $this->validateStoreApiCall(
72 | 'put',
73 | 'agents/profile',
74 | array(
75 | 'agent' => 'agent-as-json',
76 | 'profileId' => 'profile-id',
77 | ),
78 | 204,
79 | '',
80 | $document->getData(),
81 | array(array('data' => $profile->getAgent(), 'result' => 'agent-as-json'))
82 | );
83 |
84 | $this->client->createOrReplaceDocument($document);
85 | }
86 |
87 | public function testDeleteDocument()
88 | {
89 | $profile = $this->createAgentProfile();
90 |
91 | $this->validateRequest(
92 | 'delete',
93 | 'agents/profile',
94 | array(
95 | 'agent' => 'agent-as-json',
96 | 'profileId' => 'profile-id',
97 | ),
98 | ''
99 | );
100 | $this->validateSerializer(array(array('data' => $profile->getAgent(), 'result' => 'agent-as-json')));
101 |
102 | $this->client->deleteDocument(
103 | $profile
104 | );
105 | }
106 |
107 | public function testGetDocument()
108 | {
109 | $document = DocumentFixtures::getAgentProfileDocument();
110 | $profile = $document->getAgentProfile();
111 |
112 | $this->validateRetrieveApiCall(
113 | 'get',
114 | 'agents/profile',
115 | array(
116 | 'agent' => 'agent-as-json',
117 | 'profileId' => 'profile-id',
118 | ),
119 | 200,
120 | 'DocumentData',
121 | $document->getData(),
122 | array(array('data' => $profile->getAgent(), 'result' => 'agent-as-json'))
123 | );
124 |
125 | $document = $this->client->getDocument($profile);
126 |
127 | $this->assertInstanceOf(AgentProfileDocument::class, $document);
128 | $this->assertEquals($profile, $document->getAgentProfile());
129 | }
130 |
131 | private function createAgentProfile()
132 | {
133 | $agent = new Agent(InverseFunctionalIdentifier::withMbox(IRI::fromString('mailto:christian@example.com')));
134 | $profile = new AgentProfile('profile-id', $agent);
135 |
136 | return $profile;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/tests/Api/ApiClientTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Tests\Api;
13 |
14 | use PHPUnit\Framework\TestCase;
15 | use Psr\Http\Message\RequestInterface;
16 | use Psr\Http\Message\ResponseInterface;
17 | use Symfony\Component\Serializer\SerializerInterface;
18 | use Xabbuh\XApi\Client\Request\HandlerInterface;
19 | use Xabbuh\XApi\Common\Exception\NotFoundException;
20 | use Xabbuh\XApi\Serializer\SerializerRegistry;
21 | use Xabbuh\XApi\Serializer\Symfony\ActorSerializer;
22 | use Xabbuh\XApi\Serializer\Symfony\DocumentDataSerializer;
23 | use Xabbuh\XApi\Serializer\Symfony\StatementResultSerializer;
24 | use Xabbuh\XApi\Serializer\Symfony\StatementSerializer;
25 |
26 | /**
27 | * @author Christian Flothmann
28 | */
29 | abstract class ApiClientTest extends TestCase
30 | {
31 | /**
32 | * @var HandlerInterface|\PHPUnit_Framework_MockObject_MockObject
33 | */
34 | protected $requestHandler;
35 |
36 | /**
37 | * @var SerializerInterface|\PHPUnit_Framework_MockObject_MockObject
38 | */
39 | protected $serializer;
40 |
41 | /**
42 | * @var SerializerRegistry
43 | */
44 | protected $serializerRegistry;
45 |
46 | protected function setUp(): void
47 | {
48 | $this->requestHandler = $this->getMockBuilder(HandlerInterface::class)->getMock();
49 | $this->serializer = $this->getMockBuilder(SerializerInterface::class)->getMock();
50 | $this->serializerRegistry = $this->createSerializerRegistry();
51 | }
52 |
53 | protected function createSerializerRegistry()
54 | {
55 | $registry = new SerializerRegistry();
56 | $registry->setStatementSerializer(new StatementSerializer($this->serializer));
57 | $registry->setStatementResultSerializer(new StatementResultSerializer($this->serializer));
58 | $registry->setActorSerializer(new ActorSerializer($this->serializer));
59 | $registry->setDocumentDataSerializer(new DocumentDataSerializer($this->serializer));
60 |
61 | return $registry;
62 | }
63 |
64 | protected function validateSerializer(array $serializerMap)
65 | {
66 | $this
67 | ->serializer
68 | ->expects($this->any())
69 | ->method('serialize')
70 | ->willReturnCallback(function ($data) use ($serializerMap) {
71 | foreach ($serializerMap as $entry) {
72 | if ($data == $entry['data']) {
73 | return $entry['result'];
74 | }
75 | }
76 |
77 | return '';
78 | });
79 | }
80 |
81 | protected function validateRequest($method, $uri, array $urlParameters, $body = null)
82 | {
83 | $request = $this->getMockBuilder(RequestInterface::class)->getMock();
84 | $this
85 | ->requestHandler
86 | ->expects($this->once())
87 | ->method('createRequest')
88 | ->with($method, $uri, $urlParameters, $body)
89 | ->willReturn($request);
90 |
91 | return $request;
92 | }
93 |
94 | protected function validateRetrieveApiCall($method, $uri, array $urlParameters, $statusCode, $type, $transformedResult, array $serializerMap = array())
95 | {
96 | $rawResponse = 'the-server-response';
97 | $response = $this->getMockBuilder(ResponseInterface::class)->getMock();
98 | $response->expects($this->any())->method('getStatusCode')->willReturn($statusCode);
99 | $response->expects($this->any())->method('getHeader')->with('Content-Type')->willReturn(array('application/json'));
100 | $response->expects($this->any())->method('getBody')->willReturn($rawResponse);
101 | $request = $this->validateRequest($method, $uri, $urlParameters);
102 |
103 | if (404 === $statusCode) {
104 | $this
105 | ->requestHandler
106 | ->expects($this->once())
107 | ->method('executeRequest')
108 | ->with($request)
109 | ->willThrowException(new NotFoundException('Not found'));
110 | } else {
111 | $this
112 | ->requestHandler
113 | ->expects($this->once())
114 | ->method('executeRequest')
115 | ->with($request)
116 | ->willReturn($response);
117 | }
118 |
119 | $this->validateSerializer($serializerMap);
120 |
121 | if ($statusCode < 400) {
122 | $this->serializer
123 | ->expects($this->once())
124 | ->method('deserialize')
125 | ->with($rawResponse, 'Xabbuh\XApi\Model\\'.$type, 'json')
126 | ->willReturn($transformedResult);
127 | }
128 | }
129 |
130 | protected function validateStoreApiCall($method, $uri, array $urlParameters, $statusCode, $rawResponse, $object, array $serializerMap = array())
131 | {
132 | $rawRequest = 'the-request-body';
133 | $response = $this->getMockBuilder(ResponseInterface::class)->getMock();
134 | $response->expects($this->any())->method('getStatusCode')->willReturn($statusCode);
135 | $response->expects($this->any())->method('getBody')->willReturn($rawResponse);
136 | $request = $this->validateRequest($method, $uri, $urlParameters, $rawRequest);
137 | $this
138 | ->requestHandler
139 | ->expects($this->once())
140 | ->method('executeRequest')
141 | ->with($request, array($statusCode))
142 | ->willReturn($response);
143 | $serializerMap[] = array('data' => $object, 'result' => $rawRequest);
144 | $this->validateSerializer($serializerMap);
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/tests/Api/StateApiClientTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Tests\Api;
13 |
14 | use Xabbuh\XApi\Client\Api\StateApiClient;
15 | use Xabbuh\XApi\DataFixtures\DocumentFixtures;
16 | use Xabbuh\XApi\Model\Activity;
17 | use Xabbuh\XApi\Model\Agent;
18 | use Xabbuh\XApi\Model\InverseFunctionalIdentifier;
19 | use Xabbuh\XApi\Model\IRI;
20 | use Xabbuh\XApi\Model\State;
21 | use Xabbuh\XApi\Model\StateDocument;
22 | use Xabbuh\XApi\Serializer\Symfony\ActorSerializer;
23 | use Xabbuh\XApi\Serializer\Symfony\DocumentDataSerializer;
24 |
25 | /**
26 | * @author Christian Flothmann
27 | */
28 | class StateApiClientTest extends ApiClientTest
29 | {
30 | /**
31 | * @var StateApiClient
32 | */
33 | private $client;
34 |
35 | protected function setUp(): void
36 | {
37 | parent::setUp();
38 | $this->client = new StateApiClient(
39 | $this->requestHandler,
40 | '1.0.1',
41 | new DocumentDataSerializer($this->serializer),
42 | new ActorSerializer($this->serializer)
43 | );
44 | }
45 |
46 | public function testCreateOrUpdateDocument()
47 | {
48 | $document = DocumentFixtures::getStateDocument();
49 |
50 | $this->validateStoreApiCall(
51 | 'post',
52 | 'activities/state',
53 | array(
54 | 'activityId' => 'activity-id',
55 | 'agent' => 'agent-as-json',
56 | 'stateId' => 'state-id',
57 | ),
58 | 204,
59 | '',
60 | $document->getData(),
61 | array(array('data' => $document->getState()->getActor(), 'result' => 'agent-as-json'))
62 | );
63 |
64 | $this->client->createOrUpdateDocument($document);
65 | }
66 |
67 | public function testCreateOrReplaceDocument()
68 | {
69 | $document = DocumentFixtures::getStateDocument();
70 |
71 | $this->validateStoreApiCall(
72 | 'put',
73 | 'activities/state',
74 | array(
75 | 'activityId' => 'activity-id',
76 | 'agent' => 'agent-as-json',
77 | 'stateId' => 'state-id',
78 | ),
79 | 204,
80 | '',
81 | $document->getData(),
82 | array(array('data' => $document->getState()->getActor(), 'result' => 'agent-as-json'))
83 | );
84 |
85 | $this->client->createOrReplaceDocument($document);
86 | }
87 |
88 | public function testDeleteDocument()
89 | {
90 | $state = $this->createState();
91 |
92 | $this->validateRequest(
93 | 'delete',
94 | 'activities/state',
95 | array(
96 | 'activityId' => 'activity-id',
97 | 'agent' => 'agent-as-json',
98 | 'stateId' => 'state-id',
99 | ),
100 | ''
101 | );
102 | $this->validateSerializer(array(array('data' => $state->getActor(), 'result' => 'agent-as-json')));
103 |
104 | $this->client->deleteDocument($state);
105 | }
106 |
107 | public function testGetDocument()
108 | {
109 | $document = DocumentFixtures::getStateDocument();
110 | $state = $document->getState();
111 |
112 | $this->validateRetrieveApiCall(
113 | 'get',
114 | 'activities/state',
115 | array(
116 | 'activityId' => 'activity-id',
117 | 'agent' => 'agent-as-json',
118 | 'stateId' => 'state-id',
119 | ),
120 | 200,
121 | 'DocumentData',
122 | $document->getData(),
123 | array(array('data' => $state->getActor(), 'result' => 'agent-as-json'))
124 | );
125 |
126 | $document = $this->client->getDocument($state);
127 |
128 | $this->assertInstanceOf(StateDocument::class, $document);
129 | $this->assertEquals($state, $document->getState());
130 | }
131 |
132 | private function createState()
133 | {
134 | $agent = new Agent(InverseFunctionalIdentifier::withMbox(IRI::fromString('mailto:alice@example.com')));
135 | $activity = new Activity(IRI::fromString('activity-id'));
136 | $state = new State($activity, $agent, 'state-id');
137 |
138 | return $state;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/tests/Api/StatementsApiClientTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Xabbuh\XApi\Client\Tests\Api;
13 |
14 | use Xabbuh\XApi\Client\Api\StatementsApiClient;
15 | use Xabbuh\XApi\Common\Exception\NotFoundException;
16 | use Xabbuh\XApi\DataFixtures\StatementFixtures;
17 | use Xabbuh\XApi\Model\Agent;
18 | use Xabbuh\XApi\Model\InverseFunctionalIdentifier;
19 | use Xabbuh\XApi\Model\IRI;
20 | use Xabbuh\XApi\Model\IRL;
21 | use Xabbuh\XApi\Model\Statement;
22 | use Xabbuh\XApi\Model\StatementId;
23 | use Xabbuh\XApi\Model\StatementReference;
24 | use Xabbuh\XApi\Model\StatementResult;
25 | use Xabbuh\XApi\Model\StatementsFilter;
26 | use Xabbuh\XApi\Model\Verb;
27 | use Xabbuh\XApi\Serializer\Symfony\ActorSerializer;
28 | use Xabbuh\XApi\Serializer\Symfony\StatementResultSerializer;
29 | use Xabbuh\XApi\Serializer\Symfony\StatementSerializer;
30 |
31 | /**
32 | * @author Christian Flothmann
33 | */
34 | class StatementsApiClientTest extends ApiClientTest
35 | {
36 | /**
37 | * @var StatementsApiClient
38 | */
39 | private $client;
40 |
41 | protected function setUp(): void
42 | {
43 | parent::setUp();
44 | $this->client = new StatementsApiClient(
45 | $this->requestHandler,
46 | '1.0.1',
47 | new StatementSerializer($this->serializer),
48 | new StatementResultSerializer($this->serializer),
49 | new ActorSerializer($this->serializer)
50 | );
51 | }
52 |
53 | public function testStoreStatement()
54 | {
55 | $statementId = '12345678-1234-5678-1234-567812345678';
56 | $statement = $this->createStatement();
57 | $this->validateStoreApiCall(
58 | 'post',
59 | 'statements',
60 | array(),
61 | 200,
62 | '["'.$statementId.'"]',
63 | $this->createStatement()
64 | );
65 | $returnedStatement = $this->client->storeStatement($statement);
66 | $expectedStatement = $this->createStatement($statementId);
67 |
68 | $this->assertEquals($expectedStatement, $returnedStatement);
69 | }
70 |
71 | public function testStoreStatementWithId()
72 | {
73 | $statementId = '12345678-1234-5678-1234-567812345678';
74 | $statement = $this->createStatement($statementId);
75 | $this->validateStoreApiCall(
76 | 'put',
77 | 'statements',
78 | array('statementId' => $statementId),
79 | 204,
80 | '["'.$statementId.'"]',
81 | $statement
82 | );
83 |
84 | $this->assertEquals($statement, $this->client->storeStatement($statement));
85 | }
86 |
87 | public function testStoreStatementWithIdEnsureThatTheIdIsNotOverwritten()
88 | {
89 | $statementId = '12345678-1234-5678-1234-567812345678';
90 | $statement = $this->createStatement($statementId);
91 | $this->validateStoreApiCall(
92 | 'put',
93 | 'statements',
94 | array('statementId' => $statementId),
95 | 204,
96 | '',
97 | $statement
98 | );
99 | $storedStatement = $this->client->storeStatement($statement);
100 |
101 | $this->assertEquals($statementId, $storedStatement->getId()->getValue());
102 | }
103 |
104 | public function testStoreStatements()
105 | {
106 | $statementId1 = '12345678-1234-5678-1234-567812345678';
107 | $statementId2 = '12345678-1234-5678-1234-567812345679';
108 | $statement1 = $this->createStatement();
109 | $statement2 = $this->createStatement();
110 | $this->validateStoreApiCall(
111 | 'post',
112 | 'statements',
113 | array(),
114 | '200',
115 | '["'.$statementId1.'","'.$statementId2.'"]',
116 | array($this->createStatement(), $this->createStatement())
117 | );
118 | $statements = $this->client->storeStatements(array($statement1, $statement2));
119 | $expectedStatement1 = $this->createStatement($statementId1);
120 | $expectedStatement2 = $this->createStatement($statementId2);
121 | $expectedStatements = array($expectedStatement1, $expectedStatement2);
122 |
123 | $this->assertNotContains($statements[0], array($statement1, $statement2));
124 | $this->assertNotContains($statements[1], array($statement1, $statement2));
125 | $this->assertEquals($expectedStatements, $statements);
126 | $this->assertEquals($statementId1, $statements[0]->getId()->getValue());
127 | $this->assertEquals($statementId2, $statements[1]->getId()->getValue());
128 | }
129 |
130 | public function testStoreStatementsWithNonStatementObject()
131 | {
132 | $this->expectException(\InvalidArgumentException::class);
133 |
134 | $statement1 = $this->createStatement();
135 | $statement2 = $this->createStatement();
136 |
137 | $this->client->storeStatements(array($statement1, new \stdClass(), $statement2));
138 | }
139 |
140 | public function testStoreStatementsWithNonObject()
141 | {
142 | $this->expectException(\InvalidArgumentException::class);
143 |
144 | $statement1 = $this->createStatement();
145 | $statement2 = $this->createStatement();
146 |
147 | $this->client->storeStatements(array($statement1, 'foo', $statement2));
148 | }
149 |
150 | public function testStoreStatementsWithId()
151 | {
152 | $this->expectException(\InvalidArgumentException::class);
153 |
154 | $statement1 = $this->createStatement();
155 | $statement2 = $this->createStatement('12345678-1234-5678-1234-567812345679');
156 |
157 | $this->client->storeStatements(array($statement1, $statement2));
158 | }
159 |
160 | public function testVoidStatement()
161 | {
162 | $voidedStatementId = '12345678-1234-5678-1234-567812345679';
163 | $voidingStatementId = '12345678-1234-5678-1234-567812345678';
164 | $agent = new Agent(InverseFunctionalIdentifier::withMbox(IRI::fromString('mailto:john.doe@example.com')));
165 | $statementReference = new StatementReference(StatementId::fromString($voidedStatementId));
166 | $voidingStatement = new Statement(null, $agent, Verb::createVoidVerb(), $statementReference);
167 | $voidedStatement = $this->createStatement($voidedStatementId);
168 | $this->validateStoreApiCall(
169 | 'post',
170 | 'statements',
171 | array(),
172 | 200,
173 | '["'.$voidingStatementId.'"]',
174 | $voidingStatement
175 | );
176 | $returnedVoidingStatement = $this->client->voidStatement($voidedStatement, $agent);
177 | $expectedVoidingStatement = new Statement(
178 | StatementId::fromString($voidingStatementId),
179 | $agent,
180 | Verb::createVoidVerb(),
181 | $statementReference
182 | );
183 |
184 | $this->assertEquals($expectedVoidingStatement, $returnedVoidingStatement);
185 | }
186 |
187 | public function testGetStatement()
188 | {
189 | $statementId = '12345678-1234-5678-1234-567812345678';
190 | $statement = $this->createStatement();
191 | $this->validateRetrieveApiCall(
192 | 'get',
193 | 'statements',
194 | array('statementId' => $statementId, 'attachments' => 'true'),
195 | 200,
196 | 'Statement',
197 | $statement
198 | );
199 |
200 | $this->client->getStatement(StatementId::fromString($statementId));
201 | }
202 |
203 | public function testGetStatementWithNotExistingStatement()
204 | {
205 | $this->expectException(NotFoundException::class);
206 |
207 | $statementId = '12345678-1234-5678-1234-567812345678';
208 | $this->validateRetrieveApiCall(
209 | 'get',
210 | 'statements',
211 | array('statementId' => $statementId, 'attachments' => 'true'),
212 | 404,
213 | 'Statement',
214 | 'There is no statement associated with this id'
215 | );
216 |
217 | $this->client->getStatement(StatementId::fromString($statementId));
218 | }
219 |
220 | public function testGetVoidedStatement()
221 | {
222 | $statementId = '12345678-1234-5678-1234-567812345678';
223 | $statement = $this->createStatement();
224 | $this->validateRetrieveApiCall(
225 | 'get',
226 | 'statements',
227 | array('voidedStatementId' => $statementId, 'attachments' => 'true'),
228 | 200,
229 | 'Statement',
230 | $statement
231 | );
232 |
233 | $this->client->getVoidedStatement(StatementId::fromString($statementId));
234 | }
235 |
236 | public function testGetVoidedStatementWithNotExistingStatement()
237 | {
238 | $this->expectException(NotFoundException::class);
239 |
240 | $statementId = '12345678-1234-5678-1234-567812345678';
241 | $this->validateRetrieveApiCall(
242 | 'get',
243 | 'statements',
244 | array('voidedStatementId' => $statementId, 'attachments' => 'true'),
245 | 404,
246 | 'Statement',
247 | 'There is no statement associated with this id'
248 | );
249 |
250 | $this->client->getVoidedStatement(StatementId::fromString($statementId));
251 | }
252 |
253 | public function testGetStatements()
254 | {
255 | $statementResult = $this->createStatementResult();
256 | $this->validateRetrieveApiCall(
257 | 'get',
258 | 'statements',
259 | array(),
260 | 200,
261 | 'StatementResult',
262 | $statementResult
263 | );
264 |
265 | $this->assertEquals($statementResult, $this->client->getStatements());
266 | }
267 |
268 | public function testGetStatementsWithStatementsFilter()
269 | {
270 | $filter = new StatementsFilter();
271 | $filter->limit(10)->ascending();
272 | $statementResult = $this->createStatementResult();
273 | $this->validateRetrieveApiCall(
274 | 'get',
275 | 'statements',
276 | array('limit' => 10, 'ascending' => 'true'),
277 | 200,
278 | 'StatementResult',
279 | $statementResult
280 | );
281 |
282 | $this->assertEquals($statementResult, $this->client->getStatements($filter));
283 | }
284 |
285 | public function testGetStatementsWithAgentInStatementsFilter()
286 | {
287 | // {"mbox":"mailto:alice@example.com","objectType":"Agent"}
288 | $filter = new StatementsFilter();
289 | $agent = new Agent(InverseFunctionalIdentifier::withMbox(IRI::fromString('mailto:alice@example.com')));
290 | $filter->byActor($agent);
291 | $statementResult = $this->createStatementResult();
292 | $agentJson = '{"mbox":"mailto:alice@example.com","objectType":"Agent"}';
293 | $this->serializer
294 | ->expects($this->once())
295 | ->method('serialize')
296 | ->with($agent, 'json')
297 | ->willReturn($agentJson);
298 | $this->validateRetrieveApiCall(
299 | 'get',
300 | 'statements',
301 | array('agent' => $agentJson),
302 | 200,
303 | 'StatementResult',
304 | $statementResult
305 | );
306 |
307 | $this->assertEquals($statementResult, $this->client->getStatements($filter));
308 | }
309 |
310 | public function testGetStatementsWithVerbInStatementsFilter()
311 | {
312 | $filter = new StatementsFilter();
313 | $verb = new Verb(IRI::fromString('http://adlnet.gov/expapi/verbs/attended'));
314 | $filter->byVerb($verb);
315 | $statementResult = $this->createStatementResult();
316 | $this->validateRetrieveApiCall(
317 | 'get',
318 | 'statements',
319 | array('verb' => 'http://adlnet.gov/expapi/verbs/attended'),
320 | 200,
321 | 'StatementResult',
322 | $statementResult
323 | );
324 |
325 | $this->assertEquals($statementResult, $this->client->getStatements($filter));
326 | }
327 |
328 | public function testGetNextStatements()
329 | {
330 | $moreUrl = '/xapi/statements/more/b381d8eca64a61a42c7b9b4ecc2fabb6';
331 | $previousStatementResult = new StatementResult(array(), IRL::fromString($moreUrl));
332 | $this->validateRetrieveApiCall(
333 | 'get',
334 | $moreUrl,
335 | array(),
336 | 200,
337 | 'StatementResult',
338 | $previousStatementResult
339 | );
340 |
341 | $statementResult = $this->client->getNextStatements($previousStatementResult);
342 |
343 | $this->assertInstanceOf(StatementResult::class, $statementResult);
344 | }
345 |
346 | /**
347 | * @param int $id
348 | *
349 | * @return Statement
350 | */
351 | private function createStatement($id = null)
352 | {
353 | $statement = StatementFixtures::getMinimalStatement($id);
354 |
355 | if (null === $id) {
356 | $statement = $statement->withId(null);
357 | }
358 |
359 | return $statement;
360 | }
361 |
362 | /**
363 | * @return StatementResult
364 | */
365 | private function createStatementResult()
366 | {
367 | return new StatementResult(array());
368 | }
369 | }
370 |
--------------------------------------------------------------------------------