This commit is contained in:
Xes
2025-08-14 22:41:49 +02:00
parent 2de81ccc46
commit 8ce45119b6
39774 changed files with 4309466 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
/composer.lock
/phpunit.xml
/vendor

View File

@@ -0,0 +1,34 @@
sudo: false
language: php
cache:
directories:
- $HOME/.composer/cache/files
- $HOME/.phpunit
env:
global:
- SYMFONY_PHPUNIT_DIR="$HOME/.phpunit"
matrix:
fast_finish: true
include:
- php: 5.6
- php: 7.0
- php: 7.1
- php: 7.2
env: SYMFONY_PHPUNIT_VERSION=7.5
before_install:
- composer self-update
install:
- if [ "$deps" = "low" ]; then composer update --prefer-lowest --prefer-stable; fi
- if [ "$deps" = "" ]; then composer install; fi
- vendor/bin/simple-phpunit install
script:
- vendor/bin/simple-phpunit --coverage-clover=coverage.clover
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover

View File

@@ -0,0 +1,58 @@
CHANGELOG
=========
0.4.0
-----
* The `XApi\Repository\Doctrine\Mapping\Object` class was renamed to
`XApi\Repository\Doctrine\Mapping\StatementObject` for compatibility with
PHP 7.2.
* dropped suppport for PHP < 5.6 and HHVM
* made the package compatible with `3.x` releases of `ramsey/uuid`
* allow `2.x` and `3.x` releases of the `php-xapi/model` package too
0.3.0
-----
* Added mapping classes for all statement properties.
* The `MappedStatement` and `MappedVerb` classes have been removed from the
`php-xapi/model` package. They have been replaced with the new `Statement`
and `Verb` classes in the `XApi\Repository\Doctrine\Mapping` namespace of
this package. Consequently, the `MappedStatementRepository` class has been
removed. It was replaced with a new `StatementRepository` class in the
`XApi\Repository\Doctrine\Repository\Mapping` namespace.
* The requirements for `php-xapi/model` and `php-xapi/test-fixtures` have
been bumped to `^1.0` to make use of their stable releases.
* The required version of the `php-xapi/repository-api` package has been
raised to `^0.3`.
0.2.1
-----
* fixed namespace for base unit test case class `MappedStatementRepositoryTest`
0.2.0
-----
* moved base functional `StatementRepositoryTest` test case class to the
`XApi\Repository\Doctrine\Test\Functional` namespace
* changed base namespace of all classes from `Xabbuh\XApi\Storage\Doctrine` to
`XApi\Repository\Doctrine`
* added compatibility for version 0.2 of `php-xapi/repository-api`
0.1.0
-----
First release providing common functions for Doctrine based xAPI learning
record store backends.
This package replaces the `xabbuh/xapi-doctrine-storage` package which is now
deprecated and should no longer be used.

View File

@@ -0,0 +1,19 @@
Copyright (c) 2014-2017 Christian Flothmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,13 @@
Common Doctrine Based xAPI Repository Classes
=============================================
[![Build Status](https://travis-ci.org/php-xapi/repository-doctrine.svg?branch=master)](https://travis-ci.org/php-xapi/repository-doctrine)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-xapi/repository-doctrine/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-xapi/repository-doctrine/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/php-xapi/repository-doctrine/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/php-xapi/repository-doctrine/?branch=master)
Common classes for Doctrine based [xAPI repository](https://github.com/php-xapi/repository-api/)
implementations.
Existing implementations:
* [MongoDB](https://github.com/php-xapi/repository-mongodb/)

View File

@@ -0,0 +1,34 @@
UPGRADE
=======
Upgrading from 0.3 to 0.4
-------------------------
* The `XApi\Repository\Doctrine\Mapping\Object` class was renamed to
`XApi\Repository\Doctrine\Mapping\StatementObject` for compatibility with
PHP 7.2.
Upgrading from 0.2 to 0.3
-------------------------
* The `MappedStatement` and `MappedVerb` classes have been removed from the
`php-xapi/model` package. They have been replaced with the new `Statement`
and `Verb` classes in the `XApi\Repository\Doctrine\Mapping` namespace of
this package. Consequently, the `MappedStatementRepository` class has been
removed. It was replaced with a new `StatementRepository` class in the
`XApi\Repository\Doctrine\Repository\Mapping` namespace.
* The requirements for `php-xapi/model` and `php-xapi/test-fixtures` have
been bumped to `^1.0` to make use of their stable releases.
* The required version of the `php-xapi/repository-api` package has been
raised to `^0.3`.
Upgrading from 0.1 to 0.2
-------------------------
* Moved base functional `StatementRepositoryTest` test case class to the
`XApi\Repository\Doctrine\Test\Functional` namespace.
* The base namespace was changed from `Xabbuh\XApi\Storage\Doctrine` to
`XApi\Repository\Doctrine`.

View File

@@ -0,0 +1,43 @@
{
"name": "php-xapi/repository-doctrine",
"description": "common classes for Doctrine based implementations of an xAPI repository",
"keywords": ["xAPI", "Tin Can API", "Experience API", "storage", "database", "repository", "document", "Doctrine"],
"homepage": "https://github.com/php-xapi/repository-doctrine/",
"license": "MIT",
"authors": [
{
"name": "Christian Flothmann",
"homepage": "https://github.com/xabbuh"
}
],
"require": {
"php": "^7.0 || ^8.3",
"doctrine/common": "~2.4",
"php-xapi/model": "^1.2 || ^2.1 || ^3.0",
"php-xapi/repository-api": "^0.3 || ^0.4",
"ramsey/uuid": "^2.9 || ^3.0"
},
"require-dev": {
"php-xapi/test-fixtures": "^1.0",
"symfony/phpunit-bridge": "^3.4 || ^4.0"
},
"minimum-stability": "dev",
"conflict": {
"xabbuh/xapi-doctrine-storage": "*"
},
"autoload": {
"psr-4": {
"XApi\\Repository\\Doctrine\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"XApi\\Repository\\Doctrine\\Tests\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "0.4.x-dev"
}
}
}

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="common classes for Doctrine based driver implementations of an xAPI storage">
<directory>./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,127 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Mapping;
use Xabbuh\XApi\Model\Account;
use Xabbuh\XApi\Model\Actor as ActorModel;
use Xabbuh\XApi\Model\Agent;
use Xabbuh\XApi\Model\Group;
use Xabbuh\XApi\Model\InverseFunctionalIdentifier;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\IRL;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class Actor
{
public $identifier;
/**
* @var string
*/
public $type;
/**
* @var string|null
*/
public $mbox;
/**
* @var string|null
*/
public $mboxSha1Sum;
/**
* @var string|null
*/
public $openId;
/**
* @var string|null
*/
public $accountName;
/**
* @var string|null
*/
public $accountHomePage;
/**
* @var string|null
*/
public $name;
/**
* @var Actor[]|null
*/
public $members;
public static function fromModel(ActorModel $model)
{
$inverseFunctionalIdentifier = $model->getInverseFunctionalIdentifier();
$actor = new self();
$actor->mboxSha1Sum = $inverseFunctionalIdentifier->getMboxSha1Sum();
$actor->openId = $inverseFunctionalIdentifier->getOpenId();
$actor->name = $model->getName();
if (null !== $mbox = $inverseFunctionalIdentifier->getMbox()) {
$actor->mbox = $mbox->getValue();
}
if (null !== $account = $inverseFunctionalIdentifier->getAccount()) {
$actor->accountName = $account->getName();
$actor->accountHomePage = $account->getHomePage()->getValue();
}
if ($model instanceof Group) {
$actor->type = 'group';
$actor->members = array();
foreach ($model->getMembers() as $agent) {
$actor->members[] = Actor::fromModel($agent);
}
} else {
$actor->type = 'agent';
}
return $actor;
}
public function getModel()
{
$inverseFunctionalIdentifier = null;
if (null !== $this->mbox) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withMbox(IRI::fromString($this->mbox));
} elseif (null !== $this->mboxSha1Sum) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withMboxSha1Sum($this->mboxSha1Sum);
} elseif (null !== $this->openId) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withOpenId($this->openId);
} elseif (null !== $this->accountName && null !== $this->accountHomePage) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withAccount(new Account($this->accountName, IRL::fromString($this->accountHomePage)));
}
if ('group' === $this->type) {
$members = array();
foreach ($this->members as $agent) {
$members[] = $agent->getModel();
}
return new Group($inverseFunctionalIdentifier, $this->name, $members);
}
return new Agent($inverseFunctionalIdentifier, $this->name);
}
}

View File

@@ -0,0 +1,123 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Mapping;
use Xabbuh\XApi\Model\Attachment as AttachmentModel;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\IRL;
use Xabbuh\XApi\Model\LanguageMap;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class Attachment
{
public $identifier;
public $statement;
/**
* @var string
*/
public $usageType;
/**
* @var string
*/
public $contentType;
/**
* @var int
*/
public $length;
/**
* @var string
*/
public $sha2;
/**
* @var array
*/
public $display;
/**
* @var bool
*/
public $hasDescription;
/**
* @var array|null
*/
public $description;
/**
* @var string|null
*/
public $fileUrl;
/**
* @var string|null
*/
public $content;
public static function fromModel(AttachmentModel $model)
{
$attachment = new self();
$attachment->usageType = $model->getUsageType()->getValue();
$attachment->contentType = $model->getContentType();
$attachment->length = $model->getLength();
$attachment->sha2 = $model->getSha2();
$attachment->display = array();
if (null !== $model->getFileUrl()) {
$attachment->fileUrl = $model->getFileUrl()->getValue();
}
$attachment->content = $model->getContent();
$display = $model->getDisplay();
foreach ($display->languageTags() as $languageTag) {
$attachment->display[$languageTag] = $display[$languageTag];
}
if (null !== $description = $model->getDescription()) {
$attachment->hasDescription = true;
$attachment->description = array();
foreach ($description->languageTags() as $languageTag) {
$attachment->description[$languageTag] = $description[$languageTag];
}
} else {
$attachment->hasDescription = false;
}
return $attachment;
}
public function getModel()
{
$description = null;
$fileUrl = null;
if ($this->hasDescription) {
$description = LanguageMap::create($this->description);
}
if (null !== $this->fileUrl) {
$fileUrl = IRL::fromString($this->fileUrl);
}
return new AttachmentModel(IRI::fromString($this->usageType), $this->contentType, $this->length, $this->sha2, LanguageMap::create($this->display), $description, $fileUrl, $this->content);
}
}

View File

@@ -0,0 +1,232 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Mapping;
use Xabbuh\XApi\Model\Context as ContextModel;
use Xabbuh\XApi\Model\ContextActivities;
use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Model\StatementReference;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class Context
{
public $identifier;
/**
* @var string|null
*/
public $registration;
/**
* @var StatementObject|null
*/
public $instructor;
/**
* @var StatementObject|null
*/
public $team;
/**
* @var bool|null
*/
public $hasContextActivities;
/**
* @var StatementObject[]|null
*/
public $parentActivities;
/**
* @var StatementObject[]|null
*/
public $groupingActivities;
/**
* @var StatementObject[]|null
*/
public $categoryActivities;
/**
* @var StatementObject[]|null
*/
public $otherActivities;
/**
* @var string|null
*/
public $revision;
/**
* @var string|null
*/
public $platform;
/**
* @var string|null
*/
public $language;
/**
* @var string|null
*/
public $statement;
/**
* @var Extensions|null
*/
public $extensions;
public static function fromModel(ContextModel $model)
{
$context = new self();
$context->registration = $model->getRegistration();
$context->revision = $model->getRevision();
$context->platform = $model->getPlatform();
$context->language = $model->getLanguage();
if (null !== $instructor = $model->getInstructor()) {
$context->instructor = StatementObject::fromModel($instructor);
}
if (null !== $team = $model->getTeam()) {
$context->team = StatementObject::fromModel($team);
}
if (null !== $contextActivities = $model->getContextActivities()) {
$context->hasContextActivities = true;
if (null !== $parentActivities = $contextActivities->getParentActivities()) {
$context->parentActivities = array();
foreach ($parentActivities as $parentActivity) {
$activity = StatementObject::fromModel($parentActivity);
$activity->parentContext = $context;
$context->parentActivities[] = $activity;
}
}
if (null !== $groupingActivities = $contextActivities->getGroupingActivities()) {
$context->groupingActivities = array();
foreach ($groupingActivities as $groupingActivity) {
$activity = StatementObject::fromModel($groupingActivity);
$activity->groupingContext = $context;
$context->groupingActivities[] = $activity;
}
}
if (null !== $categoryActivities = $contextActivities->getCategoryActivities()) {
$context->categoryActivities = array();
foreach ($categoryActivities as $categoryActivity) {
$activity = StatementObject::fromModel($categoryActivity);
$activity->categoryContext = $context;
$context->categoryActivities[] = $activity;
}
}
if (null !== $otherActivities = $contextActivities->getOtherActivities()) {
$context->otherActivities = array();
foreach ($otherActivities as $otherActivity) {
$activity = StatementObject::fromModel($otherActivity);
$activity->otherContext = $context;
$context->otherActivities[] = $activity;
}
}
} else {
$context->hasContextActivities = false;
}
if (null !== $statementReference = $model->getStatement()) {
$context->statement = $statementReference->getStatementId()->getValue();
}
if (null !== $contextExtensions = $model->getExtensions()) {
$context->extensions = Extensions::fromModel($contextExtensions);
}
return $context;
}
public function getModel()
{
$context = new ContextModel();
if (null !== $this->registration) {
$context = $context->withRegistration($this->registration);
}
if (null !== $this->revision) {
$context = $context->withRevision($this->revision);
}
if (null !== $this->platform) {
$context = $context->withPlatform($this->platform);
}
if (null !== $this->language) {
$context = $context->withLanguage($this->language);
}
if (null !== $this->instructor) {
$context = $context->withInstructor($this->instructor->getModel());
}
if (null !== $this->team) {
$context = $context->withTeam($this->team->getModel());
}
if ($this->hasContextActivities) {
$contextActivities = new ContextActivities();
if (null !== $this->parentActivities) {
foreach ($this->parentActivities as $contextParentActivity) {
$contextActivities = $contextActivities->withAddedParentActivity($contextParentActivity->getModel());
}
}
if (null !== $this->groupingActivities) {
foreach ($this->groupingActivities as $contextGroupingActivity) {
$contextActivities = $contextActivities->withAddedGroupingActivity($contextGroupingActivity->getModel());
}
}
if (null !== $this->categoryActivities) {
foreach ($this->categoryActivities as $contextCategoryActivity) {
$contextActivities = $contextActivities->withAddedCategoryActivity($contextCategoryActivity->getModel());
}
}
if (null !== $this->otherActivities) {
foreach ($this->otherActivities as $contextOtherActivity) {
$contextActivities = $contextActivities->withAddedOtherActivity($contextOtherActivity->getModel());
}
}
$context = $context->withContextActivities($contextActivities);
}
if (null !== $this->statement) {
$context = $context->withStatement(new StatementReference(StatementId::fromString($this->statement)));
}
if (null !== $this->extensions) {
$context = $context->withExtensions($this->extensions->getModel());
}
return $context;
}
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Mapping;
use Xabbuh\XApi\Model\Extensions as ExtensionsModel;
use Xabbuh\XApi\Model\IRI;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class Extensions
{
public $identifier;
public $extensions;
public static function fromModel(ExtensionsModel $model)
{
$extensions = new self();
$extensions->extensions = array();
foreach ($model->getExtensions() as $key) {
$extensions->extensions[$key->getValue()] = $model[$key];
}
return $extensions;
}
public function getModel()
{
$extensions = new \SplObjectStorage();
foreach ($this->extensions as $key => $extension) {
$extensions->attach(IRI::fromString($key), $extension);
}
return new ExtensionsModel($extensions);
}
}

View File

@@ -0,0 +1,114 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Mapping;
use Xabbuh\XApi\Model\Result as ResultModel;
use Xabbuh\XApi\Model\Score;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class Result
{
public $identifier;
/**
* @var bool
*/
public $hasScore;
/**
* @var float|null
*/
public $scaled;
/**
* @var float|null
*/
public $raw;
/**
* @var float|null
*/
public $min;
/**
* @var float|null
*/
public $max;
/**
* @var bool|null
*/
public $success;
/**
* @var bool|null
*/
public $completion;
/**
* @var string|null
*/
public $response;
/**
* @var string|null
*/
public $duration;
/**
* @var Extensions|null
*/
public $extensions;
public static function fromModel(ResultModel $model)
{
$result = new self();
$result->success = $model->getSuccess();
$result->completion = $model->getCompletion();
$result->response = $model->getResponse();
$result->duration = $model->getDuration();
if (null !== $score = $model->getScore()) {
$result->hasScore = true;
$result->scaled = $score->getScaled();
$result->raw = $score->getRaw();
$result->min = $score->getMin();
$result->max = $score->getMax();
} else {
$result->hasScore = false;
}
if (null !== $extensions = $model->getExtensions()) {
$result->extensions = Extensions::fromModel($extensions);
}
return $result;
}
public function getModel()
{
$score = null;
$extensions = null;
if ($this->hasScore) {
$score = new Score($this->scaled, $this->raw, $this->min, $this->max);
}
if (null !== $this->extensions) {
$extensions = $this->extensions->getModel();
}
return new ResultModel($score, $this->success, $this->completion, $this->response, $this->duration, $extensions);
}
}

View File

@@ -0,0 +1,169 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Mapping;
use Xabbuh\XApi\Model\Statement as StatementModel;
use Xabbuh\XApi\Model\StatementId;
/**
* A {@link Statement} mapped to a storage backend.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class Statement
{
/**
* @var string
*/
public $id;
/**
* @var StatementObject
*/
public $actor;
/**
* @var Verb
*/
public $verb;
/**
* @var StatementObject
*/
public $object;
/**
* @var Result
*/
public $result;
/**
* @var StatementObject
*/
public $authority;
/**
* @var int
*/
public $created;
/**
* @var int
*/
public $stored;
/**
* @var Context
*/
public $context;
/**
* @var bool
*/
public $hasAttachments;
/**
* @var Attachment[]|null
*/
public $attachments;
public static function fromModel(StatementModel $model)
{
$statement = new self();
$statement->id = $model->getId()->getValue();
$statement->actor = StatementObject::fromModel($model->getActor());
$statement->verb = Verb::fromModel($model->getVerb());
$statement->object = StatementObject::fromModel($model->getObject());
if (null !== $model->getCreated()) {
$statement->created = $model->getCreated()->getTimestamp();
}
if (null !== $result = $model->getResult()) {
$statement->result = Result::fromModel($result);
}
if (null !== $authority = $model->getAuthority()) {
$statement->authority = StatementObject::fromModel($authority);
}
if (null !== $context = $model->getContext()) {
$statement->context = Context::fromModel($context);
}
if (null !== $attachments = $model->getAttachments()) {
$statement->hasAttachments = true;
$statement->attachments = array();
foreach ($attachments as $attachment) {
$mappedAttachment = Attachment::fromModel($attachment);
$mappedAttachment->statement = $statement;
$statement->attachments[] = $mappedAttachment;
}
} else {
$statement->hasAttachments = false;
}
return $statement;
}
public function getModel()
{
$result = null;
$authority = null;
$created = null;
$stored = null;
$context = null;
$attachments = null;
if (null !== $this->result) {
$result = $this->result->getModel();
}
if (null !== $this->authority) {
$authority = $this->authority->getModel();
}
if (null !== $this->created) {
$created = new \DateTime('@'.$this->created);
}
if (null !== $this->stored) {
$stored = new \DateTime('@'.$this->stored);
}
if (null !== $this->context) {
$context = $this->context->getModel();
}
if ($this->hasAttachments) {
$attachments = array();
foreach ($this->attachments as $attachment) {
$attachments[] = $attachment->getModel();
}
}
return new StatementModel(
StatementId::fromString($this->id),
$this->actor->getModel(),
$this->verb->getModel(),
$this->object->getModel(),
$result,
$authority,
$created,
$stored,
$context,
$attachments
);
}
}

View File

@@ -0,0 +1,393 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Mapping;
use Xabbuh\XApi\Model\Account;
use Xabbuh\XApi\Model\Activity;
use Xabbuh\XApi\Model\Actor as ActorModel;
use Xabbuh\XApi\Model\Agent;
use Xabbuh\XApi\Model\Definition;
use Xabbuh\XApi\Model\Group;
use Xabbuh\XApi\Model\InverseFunctionalIdentifier;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\IRL;
use Xabbuh\XApi\Model\LanguageMap;
use Xabbuh\XApi\Model\Object as ObjectModel;
use Xabbuh\XApi\Model\StatementObject as StatementObjectModel;
use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Model\StatementReference;
use Xabbuh\XApi\Model\SubStatement;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class StatementObject
{
const TYPE_ACTIVITY = 'activity';
const TYPE_AGENT = 'agent';
const TYPE_GROUP = 'group';
const TYPE_STATEMENT_REFERENCE = 'statement_reference';
const TYPE_SUB_STATEMENT = 'sub_statement';
public $identifier;
/**
* @var string
*/
public $type;
/**
* @var string|null
*/
public $activityId;
/**
* @var bool|null
*/
public $hasActivityDefinition;
/**
* @var bool|null
*/
public $hasActivityName;
/**
* @var array|null
*/
public $activityName;
/**
* @var bool|null
*/
public $hasActivityDescription;
/**
* @var array|null
*/
public $activityDescription;
/**
* @var string|null
*/
public $activityType;
/**
* @var string|null
*/
public $activityMoreInfo;
/**
* @var Extensions|null
*/
public $activityExtensions;
/**
* @var string|null
*/
public $mbox;
/**
* @var string|null
*/
public $mboxSha1Sum;
/**
* @var string|null
*/
public $openId;
/**
* @var string|null
*/
public $accountName;
/**
* @var string|null
*/
public $accountHomePage;
/**
* @var string|null
*/
public $name;
/**
* @var StatementObject[]|null
*/
public $members;
/**
* @var StatementObject|null
*/
public $group;
/**
* @var string|null
*/
public $referencedStatementId;
/**
* @var StatementObject|null
*/
public $actor;
/**
* @var Verb|null
*/
public $verb;
/**
* @var StatementObject|null
*/
public $object;
/**
* @var Result|null
*/
public $result;
/**
* @var Context|null
*/
public $context;
/**
* @var Statement|null
*/
public $parentContext;
/**
* @var Statement|null
*/
public $groupingContext;
/**
* @var Statement|null
*/
public $categoryContext;
/**
* @var Statement|null
*/
public $otherContext;
public static function fromModel($model)
{
if (!$model instanceof ObjectModel && !$model instanceof StatementObjectModel) {
throw new \InvalidArgumentException(sprintf('Expected a statement object but got %s', is_object($model) ? get_class($model) : gettype($model)));
}
if ($model instanceof ActorModel) {
return self::fromActor($model);
}
if ($model instanceof StatementReference) {
$object = new self();
$object->type = self::TYPE_STATEMENT_REFERENCE;
$object->referencedStatementId = $model->getStatementId()->getValue();
return $object;
}
if ($model instanceof SubStatement) {
return self::fromSubStatement($model);
}
return self::fromActivity($model);
}
public function getModel()
{
if (self::TYPE_AGENT === $this->type || self::TYPE_GROUP === $this->type) {
return $this->getActorModel();
}
if (self::TYPE_STATEMENT_REFERENCE === $this->type) {
return new StatementReference(StatementId::fromString($this->referencedStatementId));
}
if (self::TYPE_SUB_STATEMENT === $this->type) {
return $this->getSubStatementModel();
}
return $this->getActivityModel();
}
private static function fromActivity(Activity $model)
{
$object = new self();
$object->activityId = $model->getId()->getValue();
if (null !== $definition = $model->getDefinition()) {
$object->hasActivityDefinition = true;
if (null !== $name = $definition->getName()) {
$object->hasActivityName = true;
$object->activityName = array();
foreach ($name->languageTags() as $languageTag) {
$object->activityName[$languageTag] = $name[$languageTag];
}
} else {
$object->hasActivityName = false;
}
if (null !== $description = $definition->getDescription()) {
$object->hasActivityDescription = true;
$object->activityDescription = array();
foreach ($description->languageTags() as $languageTag) {
$object->activityDescription[$languageTag] = $description[$languageTag];
}
} else {
$object->hasActivityDescription = false;
}
if (null !== $type = $definition->getType()) {
$object->activityType = $type->getValue();
}
if (null !== $moreInfo = $definition->getMoreInfo()) {
$object->activityMoreInfo = $moreInfo->getValue();
}
if (null !== $extensions = $definition->getExtensions()) {
$object->activityExtensions = Extensions::fromModel($extensions);
}
} else {
$object->hasActivityDefinition = false;
}
return $object;
}
private static function fromActor(ActorModel $model)
{
$inverseFunctionalIdentifier = $model->getInverseFunctionalIdentifier();
$object = new self();
$object->mboxSha1Sum = $inverseFunctionalIdentifier->getMboxSha1Sum();
$object->openId = $inverseFunctionalIdentifier->getOpenId();
$object->name = $model->getName();
if (null !== $mbox = $inverseFunctionalIdentifier->getMbox()) {
$object->mbox = $mbox->getValue();
}
if (null !== $account = $inverseFunctionalIdentifier->getAccount()) {
$object->accountName = $account->getName();
$object->accountHomePage = $account->getHomePage()->getValue();
}
if ($model instanceof Group) {
$object->type = self::TYPE_GROUP;
$object->members = array();
foreach ($model->getMembers() as $agent) {
$object->members[] = self::fromActor($agent);
}
} else {
$object->type = self::TYPE_AGENT;
}
return $object;
}
private static function fromSubStatement(SubStatement $model)
{
$object = new self();
$object->type = self::TYPE_SUB_STATEMENT;
$object->actor = StatementObject::fromModel($model->getActor());
$object->verb = Verb::fromModel($model->getVerb());
$object->object = StatementObject::fromModel($model->getObject());
return $object;
}
private function getActivityModel()
{
$definition = null;
$type = null;
$moreInfo = null;
if ($this->hasActivityDefinition) {
$name = null;
$description = null;
$extensions = null;
if ($this->hasActivityName) {
$name = LanguageMap::create($this->activityName);
}
if ($this->hasActivityDescription) {
$description = LanguageMap::create($this->activityDescription);
}
if (null !== $this->activityType) {
$type = IRI::fromString($this->activityType);
}
if (null !== $this->activityMoreInfo) {
$moreInfo = IRL::fromString($this->activityMoreInfo);
}
if (null !== $this->activityExtensions) {
$extensions = $this->activityExtensions->getModel();
}
$definition = new Definition($name, $description, $type, $moreInfo, $extensions);
}
return new Activity(IRI::fromString($this->activityId), $definition);
}
private function getActorModel()
{
$inverseFunctionalIdentifier = null;
if (null !== $this->mbox) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withMbox(IRI::fromString($this->mbox));
} elseif (null !== $this->mboxSha1Sum) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withMboxSha1Sum($this->mboxSha1Sum);
} elseif (null !== $this->openId) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withOpenId($this->openId);
} elseif (null !== $this->accountName && null !== $this->accountHomePage) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withAccount(new Account($this->accountName, IRL::fromString($this->accountHomePage)));
}
if (self::TYPE_GROUP === $this->type) {
$members = array();
foreach ($this->members as $agent) {
$members[] = $agent->getModel();
}
return new Group($inverseFunctionalIdentifier, $this->name, $members);
}
return new Agent($inverseFunctionalIdentifier, $this->name);
}
private function getSubStatementModel()
{
$result = null;
$context = null;
return new SubStatement(
$this->actor->getModel(),
$this->verb->getModel(),
$this->object->getModel(),
$result,
$context
);
}
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Mapping;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\LanguageMap;
use Xabbuh\XApi\Model\Verb as VerbModel;
/**
* A {@link Verb} mapped to a storage backend.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class Verb
{
public $identifier;
/**
* @var string
*/
public $id;
/**
* @var array|null
*/
public $display;
public function getModel()
{
$display = null;
if (null !== $this->display) {
$display = LanguageMap::create($this->display);
}
return new VerbModel(IRI::fromString($this->id), $display);
}
public static function fromModel(VerbModel $model)
{
$verb = new self();
$verb->id = $model->getId()->getValue();
if (null !== $display = $model->getDisplay()) {
$verb->display = array();
foreach ($display->languageTags() as $languageTag) {
$verb->display[$languageTag] = $display[$languageTag];
}
}
return $verb;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Repository\Mapping;
use XApi\Repository\Doctrine\Mapping\Statement;
/**
* {@link Statement} repository interface definition.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
interface StatementRepository
{
/**
* @param array $criteria
*
* @return Statement The statement or null if no matching statement
* has been found
*/
public function findStatement(array $criteria);
/**
* @param array $criteria
*
* @return Statement[] The statements matching the given criteria
*/
public function findStatements(array $criteria);
/**
* Saves a {@link Statement} in the underlying storage.
*
* @param Statement $statement The statement being stored
* @param bool $flush Whether or not to flush the managed objects
* (i.e. write them to the data storage immediately)
*/
public function storeStatement(Statement $statement, $flush = true);
}

View File

@@ -0,0 +1,134 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Repository;
use Rhumsaa\Uuid\Uuid as RhumsaaUuid;
use Xabbuh\XApi\Common\Exception\NotFoundException;
use Xabbuh\XApi\Model\Actor;
use Xabbuh\XApi\Model\Statement;
use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Model\StatementsFilter;
use Xabbuh\XApi\Model\Uuid as ModelUuid;
use XApi\Repository\Api\StatementRepositoryInterface;
use XApi\Repository\Doctrine\Mapping\Statement as MappedStatement;
use XApi\Repository\Doctrine\Repository\Mapping\StatementRepository as MappedStatementRepository;
/**
* Doctrine based {@link Statement} repository.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class StatementRepository implements StatementRepositoryInterface
{
private $repository;
public function __construct(MappedStatementRepository $repository)
{
$this->repository = $repository;
}
/**
* {@inheritdoc}
*/
public function findStatementById(StatementId $statementId, Actor $authority = null)
{
$criteria = array('id' => $statementId->getValue());
if (null !== $authority) {
$criteria['authority'] = $authority;
}
$mappedStatement = $this->repository->findStatement($criteria);
if (null === $mappedStatement) {
throw new NotFoundException('No statements could be found matching the given criteria.');
}
$statement = $mappedStatement->getModel();
if ($statement->isVoidStatement()) {
throw new NotFoundException('The stored statement is a voiding statement.');
}
return $statement;
}
/**
* {@inheritdoc}
*/
public function findVoidedStatementById(StatementId $voidedStatementId, Actor $authority = null)
{
$criteria = array('id' => $voidedStatementId->getValue());
if (null !== $authority) {
$criteria['authority'] = $authority;
}
$mappedStatement = $this->repository->findStatement($criteria);
if (null === $mappedStatement) {
throw new NotFoundException('No voided statements could be found matching the given criteria.');
}
$statement = $mappedStatement->getModel();
if (!$statement->isVoidStatement()) {
throw new NotFoundException('The stored statement is no voiding statement.');
}
return $statement;
}
/**
* {@inheritdoc}
*/
public function findStatementsBy(StatementsFilter $criteria, Actor $authority = null)
{
$criteria = $criteria->getFilter();
if (null !== $authority) {
$criteria['authority'] = $authority;
}
$mappedStatements = $this->repository->findStatements($criteria);
$statements = array();
foreach ($mappedStatements as $mappedStatement) {
$statements[] = $mappedStatement->getModel();
}
return $statements;
}
/**
* {@inheritdoc}
*/
public function storeStatement(Statement $statement, $flush = true)
{
if (null === $statement->getId()) {
if (class_exists('Xabbuh\XApi\Model\Uuid')) {
$uuid = ModelUuid::uuid4();
} else {
$uuid = RhumsaaUuid::uuid4();
}
$statement = $statement->withId(StatementId::fromUuid($uuid));
}
$mappedStatement = MappedStatement::fromModel($statement);
$mappedStatement->stored = time();
$this->repository->storeStatement($mappedStatement, $flush);
return $statement->getId();
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Test\Functional;
use Doctrine\Persistence\ObjectManager;
use XApi\Repository\Api\Test\Functional\StatementRepositoryTest as BaseStatementRepositoryTest;
use XApi\Repository\Doctrine\Repository\Mapping\StatementRepository as MappedStatementRepository;
use XApi\Repository\Doctrine\Repository\StatementRepository;
use XApi\Repository\Doctrine\Test\StatementRepository as FreshStatementRepository;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
abstract class StatementRepositoryTest extends BaseStatementRepositoryTest
{
/**
* @var ObjectManager
*/
protected $objectManager;
/**
* @var MappedStatementRepository
*/
protected $repository;
protected function setUp()
{
$this->objectManager = $this->createObjectManager();
$this->repository = $this->createRepository();
parent::setUp();
}
protected function createStatementRepository()
{
return new FreshStatementRepository(new StatementRepository($this->repository), $this->objectManager);
}
protected function cleanDatabase()
{
foreach ($this->repository->findStatements(array()) as $statement) {
$this->objectManager->remove($statement);
}
$this->objectManager->flush();
}
/**
* @return ObjectManager
*/
abstract protected function createObjectManager();
/**
* @return string
*/
abstract protected function getStatementClassName();
private function createRepository()
{
return $this->objectManager->getRepository($this->getStatementClassName());
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Test;
use Doctrine\Common\Persistence\ObjectManager as LegacyObjectManager;
use Doctrine\Persistence\ObjectManager;
use Xabbuh\XApi\Model\Actor;
use Xabbuh\XApi\Model\Statement;
use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Model\StatementsFilter;
use XApi\Repository\Api\StatementRepositoryInterface;
/**
* Statement repository clearing the object manager between read and write operations.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class StatementRepository implements StatementRepositoryInterface
{
private $repository;
private $objectManager;
public function __construct(StatementRepositoryInterface $repository, $objectManager)
{
if (!$objectManager instanceof ObjectManager && !$objectManager instanceof LegacyObjectManager) {
throw new \TypeError(sprintf('The second argument of %s() must be an instance of %s (%s given).', __METHOD__, ObjectManager::class, is_object($objectManager) ? get_class($objectManager) : gettype($objectManager)));
}
$this->repository = $repository;
$this->objectManager = $objectManager;
}
/**
* {@inheritdoc}
*/
public function findStatementById(StatementId $statementId, Actor $authority = null)
{
$statement = $this->repository->findStatementById($statementId, $authority);
$this->objectManager->clear();
return $statement;
}
/**
* {@inheritdoc}
*/
public function findVoidedStatementById(StatementId $voidedStatementId, Actor $authority = null)
{
$statement = $this->repository->findVoidedStatementById($voidedStatementId, $authority);
$this->objectManager->clear();
return $statement;
}
/**
* {@inheritdoc}
*/
public function findStatementsBy(StatementsFilter $criteria, Actor $authority = null)
{
$statements = $this->repository->findStatementsBy($criteria, $authority);
$this->objectManager->clear();
return $statements;
}
/**
* {@inheritdoc}
*/
public function storeStatement(Statement $statement, $flush = true)
{
$statementId = $this->repository->storeStatement($statement);
$this->objectManager->clear();
return $statementId;
}
}

View File

@@ -0,0 +1,120 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Test\Unit\Repository\Mapping;
use PHPUnit\Framework\TestCase;
use Xabbuh\XApi\DataFixtures\StatementFixtures;
use XApi\Repository\Doctrine\Mapping\Statement;
use XApi\Repository\Doctrine\Repository\Mapping\StatementRepository;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
abstract class StatementRepositoryTest extends TestCase
{
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $objectManager;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $unitOfWork;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $classMetadata;
/**
* @var StatementRepository
*/
private $repository;
protected function setUp()
{
$this->objectManager = $this->createObjectManagerMock();
$this->unitOfWork = $this->createUnitOfWorkMock();
$this->classMetadata = $this->createClassMetadataMock();
$this->repository = $this->createMappedStatementRepository($this->objectManager, $this->unitOfWork, $this->classMetadata);
}
public function testStatementDocumentIsPersisted()
{
$this
->objectManager
->expects($this->once())
->method('persist')
->with($this->isInstanceOf('\XApi\Repository\Doctrine\Mapping\Statement'))
;
$mappedStatement = Statement::fromModel(StatementFixtures::getMinimalStatement());
$this->repository->storeStatement($mappedStatement, true);
}
public function testFlushIsCalledByDefault()
{
$this
->objectManager
->expects($this->once())
->method('flush')
;
$mappedStatement = Statement::fromModel(StatementFixtures::getMinimalStatement());
$this->repository->storeStatement($mappedStatement);
}
public function testCallToFlushCanBeSuppressed()
{
$this
->objectManager
->expects($this->never())
->method('flush')
;
$mappedStatement = Statement::fromModel(StatementFixtures::getMinimalStatement());
$this->repository->storeStatement($mappedStatement, false);
}
abstract protected function getObjectManagerClass();
protected function createObjectManagerMock()
{
return $this
->getMockBuilder($this->getObjectManagerClass())
->disableOriginalConstructor()
->getMock();
}
abstract protected function getUnitOfWorkClass();
protected function createUnitOfWorkMock()
{
return $this
->getMockBuilder($this->getUnitOfWorkClass())
->disableOriginalConstructor()
->getMock();
}
abstract protected function getClassMetadataClass();
protected function createClassMetadataMock()
{
return $this
->getMockBuilder($this->getClassMetadataClass())
->disableOriginalConstructor()
->getMock();
}
abstract protected function createMappedStatementRepository($objectManager, $unitOfWork, $classMetadata);
}

View File

@@ -0,0 +1,130 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\Repository\Doctrine\Tests\Unit\Repository;
use PHPUnit\Framework\TestCase;
use Rhumsaa\Uuid\Uuid as RhumsaUuid;
use Xabbuh\XApi\DataFixtures\StatementFixtures;
use Xabbuh\XApi\DataFixtures\VerbFixtures;
use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Model\StatementsFilter;
use Xabbuh\XApi\Model\Uuid as ModelUuid;
use XApi\Repository\Doctrine\Mapping\Statement as MappedStatement;
use XApi\Repository\Doctrine\Repository\StatementRepository;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class StatementRepositoryTest extends TestCase
{
/**
* @var \PHPUnit_Framework_MockObject_MockObject|\XApi\Repository\Doctrine\Repository\Mapping\StatementRepository
*/
private $mappedStatementRepository;
/**
* @var StatementRepository
*/
private $statementRepository;
protected function setUp()
{
$this->mappedStatementRepository = $this->createMappedStatementRepositoryMock();
$this->statementRepository = new StatementRepository($this->mappedStatementRepository);
}
public function testFindStatementById()
{
if (class_exists('Xabbuh\XApi\Model\Uuid')) {
$statementId = StatementId::fromUuid(ModelUuid::uuid4());
} else {
$statementId = StatementId::fromUuid(RhumsaUuid::uuid4());
}
$this
->mappedStatementRepository
->expects($this->once())
->method('findStatement')
->with(array('id' => $statementId->getValue()))
->will($this->returnValue(MappedStatement::fromModel(StatementFixtures::getMinimalStatement())));
$this->statementRepository->findStatementById($statementId);
}
public function testFindStatementsByCriteria()
{
$verb = VerbFixtures::getTypicalVerb();
$this
->mappedStatementRepository
->expects($this->once())
->method('findStatements')
->with($this->equalTo(array('verb' => $verb->getId()->getValue())))
->will($this->returnValue(array()));
$filter = new StatementsFilter();
$filter->byVerb($verb);
$this->statementRepository->findStatementsBy($filter);
}
public function testSave()
{
$statement = StatementFixtures::getMinimalStatement();
$this
->mappedStatementRepository
->expects($this->once())
->method('storeStatement')
->with(
$this->callback(function (MappedStatement $mappedStatement) use ($statement) {
$expected = MappedStatement::fromModel($statement);
$actual = clone $mappedStatement;
$actual->stored = null;
return $expected == $actual;
}),
true
);
$this->statementRepository->storeStatement($statement);
}
public function testSaveWithoutFlush()
{
$statement = StatementFixtures::getMinimalStatement();
$this
->mappedStatementRepository
->expects($this->once())
->method('storeStatement')
->with(
$this->callback(function (MappedStatement $mappedStatement) use ($statement) {
$expected = MappedStatement::fromModel($statement);
$actual = clone $mappedStatement;
$actual->stored = null;
return $expected == $actual;
}),
false
);
$this->statementRepository->storeStatement($statement, false);
}
/**
* @return \PHPUnit_Framework_MockObject_MockObject|\XApi\Repository\Doctrine\Repository\Mapping\StatementRepository
*/
protected function createMappedStatementRepositoryMock()
{
return $this
->getMockBuilder('\XApi\Repository\Doctrine\Repository\Mapping\StatementRepository')
->getMock();
}
}