Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature TR-4391 ensure PHP 8.1 support by the legacy release #321

Merged
merged 11 commits into from
Jul 18, 2022
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
fail-fast: false
matrix:
operating-system: [ ubuntu-latest ]
php-versions: [ '7.1', '7.2', '7.3', '7.4', '8.0' ]
php-versions: [ '7.1', '7.2', '7.3', '7.4', '8.0', '8.1' ]

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"GPL-2.0-only"
],
"require" : {
"php" : ">=7.1 <8.1",
"php" : ">=7.1",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
Expand Down
152 changes: 144 additions & 8 deletions qtism/common/dom/SerializableDomDocument.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2017-2020 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
* Copyright (c) 2017-2022 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
*
* @author Jérôme Bogaerts <jerome@taotesting.com>
* @license GPLv2
Expand All @@ -24,27 +24,163 @@
namespace qtism\common\dom;

use DOMDocument;
use DOMDocumentType;
use DOMElement;
use DOMImplementation;
use DOMDocumentFragment;
use DOMComment;
use DOMCDATASection;
use DOMProcessingInstruction;
use DOMText;
use DOMAttr;
use DOMEntityReference;
use DOMNode;
use DOMNodeList;
use Error;

/**
* Serializable DOM Document
*
* This class is a PHP Serializable DOMDocument implementation.
*
* @property string|null $actualEncoding
* @property $config
* @property DOMDocumentType|null $doctype
* @property DOMElement|null $documentElement
* @property string|null $documentURI
* @property string|null $encoding
* @property DOMImplementation $implementation
* @property bool $preserveWhiteSpace
* @property bool $recover
* @property bool $resolveExternals
* @property bool $standalone
* @property bool $strictErrorChecking
* @property bool $substituteEntities
* @property bool $validateOnParse
* @property string|null $version
* @property string|null $xmlEncoding
* @property bool $xmlStandalone
* @property string|null $xmlVersion
* @property int $childElementCount
* @property DOMElement|null $lastElementChild
* @property DOMElement|null $firstElementChild
*
* @method createElement(string $localName, string $value)
* @method DOMDocumentFragment createDocumentFragment()
* @method DOMText|false createTextNode(string $data)
* @method DOMComment|false createComment(string $data)
* @method DOMCDATASection|false createCDATASection(string $data)
* @method DOMProcessingInstruction|false createProcessingInstruction(string $target, string $data)
* @method DOMAttr|false createAttribute(string $localName)
* @method DOMEntityReference|false createEntityReference(string $name)
* @method DOMNodeList|false getElementsByTagName(string $qualifiedName)
* @method DOMNodeList|false importNode(DOMNode $node, bool $deep = false)
* @method DOMElement|false createElementNS(string|null $namespace, string $qualifiedName, string $value)
* @method DOMAttr|false createAttributeNS(string|null $namespace, string $qualifiedName)
* @method DOMNodeList getElementsByTagNameNS(string|null $namespace, string $localName)
* @method DOMElement|null getElementById(string $elementId)
* @method DOMNode adoptNode(DOMNode $elementId)
* @method append(...$nodes)
* @method prepend(...$nodes)
* @method normalizeDocument()
* @method renameNode(DOMNode $node, $namespace, $qualifiedName)
* @method DOMDocument|bool load(string $filename, ?int $options = null)
* @method int|false save($filename, $options = null)
* @method string|false saveXML(?DOMNode $node = null, int $options = null)
* @method bool validate()
* @method int|false xinclude(int $options = null)
* @method DOMDocument|bool loadHTML(string $source, int $options=0)
* @method DOMDocument|bool loadHTMLFile(string $filename, int $options=0)
* @method string|false saveHTML(DOMNode $node = null)
* @method int|false saveHTMLFile(string $filename)
* @method bool schemaValidate($filename, $options = null)
* @method bool schemaValidateSource($source, $flags)
* @method bool relaxNGValidate(string $filename)
* @method bool relaxNGValidateSource(string $source)
* @method bool registerNodeClass(string $baseClass, string $extendedClass)
*/
class SerializableDomDocument extends DOMDocument
class SerializableDomDocument
{
/** need to keep php 7.1 support */
private $xmlData;
private $version;
private $encoding;

private $dom;

public function __construct(string $version = '1.0', string $encoding = '')
{
$this->dom = new DOMDocument($version, $encoding);
}

/**
* @return array
*/
public function __sleep()
{
$this->xmlData = $this->saveXML();
return ['xmlData'];
$this->version = (string)$this->dom->xmlVersion;
$this->encoding = (string)$this->dom->encoding;
$this->xmlData = (string)$this;

return ['version', 'encoding', 'xmlData'];
}

public function __wakeup()
{
$this->loadXML($this->xmlData);
$this->dom = new DOMDocument($this->version, $this->encoding);
$this->dom->loadXML($this->xmlData);
}

public function __serialize(): array
{
return [
'version' => (string)$this->dom->xmlVersion,
'encoding' => (string)$this->dom->encoding,
'xmlData' => (string)$this,
];
}

public function __unserialize(array $data): void
{
$this->dom = new DOMDocument($data['version'], $data['encoding']);
$this->dom->loadXML($data['xmlData']);
}

public function __toString(): string
{
$xml = $this->dom->saveXML();
return $xml ? : '';
}

public function __call($name, $arguments)
{
if (!method_exists($this->dom, $name)) {
throw new Error(sprintf('Call to undefined method %s::%s()', __CLASS__, $name));
}

return call_user_func_array([$this->dom, $name], $arguments);
}

public function __get($name)
{
if (!property_exists($this->dom, $name)) {
trigger_error(sprintf('Undefined property: %s::%s', __CLASS__, $name), E_USER_WARNING);
}

return $this->dom->$name ?? null;
}

public function __set($name, $value)
{
$this->dom->$name = $value;

return $this->dom;
}

public function __isset(string $name): bool
{
return isset($this->dom->$name);
}

public function __unset(string $name): void
{
unset($this->dom->$name);
}
}
4 changes: 2 additions & 2 deletions qtism/data/ExternalQtiComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ public function __construct($xmlString)
* Returns the XML representation of the external component as
* a DOMDocument object.
*
* @return SerializableDomDocument A DOMDocument (serializable) object representing the content of the external component.
* return A DOMDocument (serializable) object representing the content of the external component.
* @throws RuntimeException If the root element of the XML representation is not from the target namespace or the XML could not be parsed.
*/
public function getXml()
public function getXml(): ?SerializableDomDocument
{
// Build the DOMDocument object only on demand.
if ($this->xml === null) {
Expand Down
3 changes: 1 addition & 2 deletions qtism/data/IExternal.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ interface IExternal
*
* In case of there is no external data, the implementation may return the null value.
*
* @return SerializableDomDocument
*/
public function getXml();
public function getXml(): ?SerializableDomDocument;
}
6 changes: 3 additions & 3 deletions qtism/data/content/interactions/CustomInteraction.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

namespace qtism\data\content\interactions;

use DOMDocument;
use qtism\common\dom\SerializableDomDocument;
use InvalidArgumentException;
use qtism\data\content\Block;
use qtism\data\content\Flow;
Expand Down Expand Up @@ -108,10 +108,10 @@ public function setXmlString($xmlString)
/**
* Get the XML content of the custom interaction itself and its content.
*
* @return DOMDocument A DOMDocument object representing the custom interaction.
* return A DOMDocument object representing the custom interaction.
* @throws RuntimeException If the XML content of the custom interaction and/or its content cannot be transformed into a valid DOMDocument.
*/
public function getXml()
public function getXml(): ?SerializableDomDocument
{
return $this->getExternalComponent()->getXml();
}
Expand Down
4 changes: 2 additions & 2 deletions qtism/data/expressions/operators/CustomOperator.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,10 @@ public function hasDefinition()
/**
* Get the XML content of the custom operator itself and its content.
*
* @return SerializableDomDocument A DOMDocument (serializable) object representing the custom operator itself.
* return A DOMDocument (serializable) object representing the custom operator itself.
* @throws RuntimeException If the XML content of the custom operator and/or its content cannot be transformed into a valid DOMDocument.
*/
public function getXml()
public function getXml(): ?SerializableDomDocument
{
return $this->getExternalComponent()->getXml();
}
Expand Down
4 changes: 2 additions & 2 deletions qtism/data/rules/Selection.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,10 @@ private function getExternalComponent()
/**
* Get the XML content of the selection itself and its content.
*
* @return SerializableDomDocument A DOMDocument (serializable) object representing the selection itself or null if there is no external component.
* return A DOMDocument (serializable) object representing the selection itself or null if there is no external component.
* @throws RuntimeException If the XML content of the selection and/or its content cannot be transformed into a valid DOMDocument.
*/
public function getXml()
public function getXml(): ?SerializableDomDocument
{
if (($externalComponent = $this->getExternalComponent()) !== null) {
return $this->getExternalComponent()->getXml();
Expand Down
78 changes: 73 additions & 5 deletions test/qtismtest/common/dom/SerializableDomDocumentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,88 @@

use qtism\common\dom\SerializableDomDocument;
use qtismtest\QtiSmTestCase;
use Error;

/**
* Class VersionTest
*/
class VersionTest extends QtiSmTestCase
class SerializableDomDocumentTest extends QtiSmTestCase
{
public function testSerialization()
{
$dom = new SerializableDomDocument('1.0', 'UTF-8');
$dom->load(self::samplesDir() . 'ims/items/2_2_1/choice.xml');

$ser = serialize($dom);
$ser = serialize($this->getSerializableDomDocument());
$dom = unserialize($ser);

$this::assertEquals('http://www.imsglobal.org/xsd/imsqti_v2p2', $dom->documentElement->namespaceURI);
}


public function testAccessingProperty()
{
$xmlVersion = '1.0';
$dom = $this->getSerializableDomDocument($xmlVersion);

$this->assertNotEmpty($dom->xmlVersion);
$this->assertEquals($xmlVersion, $dom->xmlVersion);
}

public function testAccessingInexistentProperty()
{
$dom = $this->getSerializableDomDocument();
$property = 'test';

$this->expectException(Error::class);
$this->expectExceptionMessage(
sprintf('Undefined property: %s::%s', SerializableDomDocument::class, $property)
);

$dom->$property;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am getting this error locally:

Time: 01:53.357, Memory: 84.00 MB

There was 1 error:

1) qtismtest\common\dom\SerializableDomDocumentTest::testAccessingInexistentProperty
Undefined property: qtism\common\dom\SerializableDomDocument::test

/Users/gabriel.soares/repos/qti-sdk/qtism/common/dom/SerializableDomDocument.php:164
/Users/gabriel.soares/repos/qti-sdk/test/qtismtest/common/dom/SerializableDomDocumentTest.php:42
phpvfscomposer:///Users/gabriel.soares/repos/qti-sdk/vendor/phpunit/phpunit/phpunit:97

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, problem with 7.1 and 7.2 with correctly catch this exception I research a solution to downgrade the $this->expectWarning(); method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test is passing now, thanks

}

public function testSettingVirtualPropertyToDom()
{
$xmlVersion = '1.0';
$dom = $this->getSerializableDomDocument($xmlVersion);

$this->assertEquals($xmlVersion, $dom->xmlVersion);

$dom->xmlVersion = '1.1';
$this->assertEquals('1.1', $dom->xmlVersion);
}

public function testCheckingIfPropertyExists()
{
$dom = $this->getSerializableDomDocument();

$this->assertTrue(isset($dom->xmlVersion));
}

public function testCallingVirtualMethods()
{
$dom = $this->getSerializableDomDocument();

$this->assertNotEmpty($dom->saveXML());
$this->assertNotEmpty((string)$dom);
}

public function testCallingNotExistedVirtualMethods()
{
$dom = $this->getSerializableDomDocument();
$method = 'saveXML2';

$this->expectException(Error::class);
$this->expectExceptionMessage(
sprintf('Call to undefined method %s::%s()', SerializableDomDocument::class, $method)
);

$dom->$method();
}

private function getSerializableDomDocument(string $version = '1.0', string $encoding = 'UTF-8'): SerializableDomDocument
{
$dom = new SerializableDomDocument($version, $encoding);
$dom->load(self::samplesDir() . 'ims/items/2_2_1/choice.xml');

return $dom;
}
}
4 changes: 2 additions & 2 deletions test/qtismtest/data/content/MathTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace qtismtest\data\content;

use DOMDocument;
use qtism\common\dom\SerializableDomDocument;
use qtism\data\content\Math;
use qtismtest\QtiSmTestCase;
use RuntimeException;
Expand Down Expand Up @@ -34,6 +34,6 @@ public function testCorrect()
{
$xml = '<m:math xmlns:m="http://www.w3.org/1998/Math/MathML"></m:math>';
$math = new Math($xml);
$this::assertInstanceOf(DOMDocument::class, $math->getXml());
$this::assertInstanceOf(SerializableDomDocument::class, $math->getXml());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace qtismtest\data\storage\xml\marshalling;

use DOMDocument;
use qtism\common\dom\SerializableDomDocument;
use qtism\data\content\Math;
use qtismtest\QtiSmTestCase;
use RuntimeException;
Expand Down Expand Up @@ -40,7 +41,7 @@ public function testUnmarshall()
$math = $this->getMarshallerFactory('2.1.0')->createMarshaller($element)->unmarshall($element);
$this::assertInstanceOf(Math::class, $math);
$xml = $math->getXml();
$this::assertInstanceOf(DOMDocument::class, $xml);
$this::assertInstanceOf(SerializableDomDocument::class, $xml);

$mathElement = $xml->documentElement;
$this::assertEquals('m', $mathElement->prefix);
Expand Down
Loading