Skip to content

Commit 973678b

Browse files
authored
Merge pull request #12 from ingenerator/2.x-class-typing
[BREAKING] Add strict typing, switch from array result to a DTO, bump PHPUnit and drop 8.2
2 parents fdc0802 + 7440d5d commit 973678b

21 files changed

+328
-403
lines changed

.gitattributes

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,10 @@ README text eol=lf
2727
*.swf binary
2828
*.zip binary
2929
*.sqlite binary
30+
31+
# Ignore paths that should not be included in an archive (eg for a distribution version)
32+
/.github export-ignore
33+
/docs export-ignore
34+
/test export-ignore
35+
phpunit.xml export-ignore
36+
koharness.php export-ignore

.github/workflows/test.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,10 @@ jobs:
1414
fail-fast: false
1515
matrix:
1616
php_version:
17-
- '8.2'
1817
- '8.3'
1918
dependencies:
2019
- 'default'
2120
include:
22-
- php_version: '8.2'
23-
dependencies: 'lowest'
2421
- php_version: '8.3'
2522
dependencies: 'lowest'
2623
steps:

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
### Unreleased
22

33
* [BREAKING] Use doctrine attributes instead of annotations for entity metadata
4+
* [BREAKING] Add hard parameter and return typehints to all classes
5+
* [BREAKING] ContentSnippetContentFilter is now an interface, use HtmlPurifierContentFilter for
6+
a runtime implementation. The ->filterContent now returns a ContentFilterResult DTO instead of
7+
a plain array.
8+
* Drop support for PHP 8.2
9+
* Internal test & config files are now excluded from distribution archives
10+
* Upgraded to PHPUnit 11 for internal tests
411

512
### v1.5.0 (2024-10-01)
613

composer.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,14 @@
1414
"require": {
1515
"composer/installers": "^1.9",
1616
"ezyang/htmlpurifier": "^4.15",
17-
"php": "~8.2.0 || ~8.3.0",
17+
"php": "~8.3.0",
1818
"ingenerator/kohana-core": "^4.7",
1919
"ingenerator/kohana-extras": "^2.0 || ^3.0",
2020
"ingenerator/kohana-view": "^4.4"
2121
},
2222
"require-dev": {
2323
"kohana/koharness": "dev-master",
24-
"phpunit/phpunit": "^9.5.5",
25-
"johnkary/phpunit-speedtrap": "^3.3"
24+
"phpunit/phpunit": "^11"
2625
},
2726
"repositories": [
2827
{

phpunit.xml

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,20 @@
33
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
44
bootstrap="test/bootstrap.php"
55
colors="true"
6-
convertErrorsToExceptions="true"
7-
convertNoticesToExceptions="true"
8-
convertWarningsToExceptions="true"
9-
convertDeprecationsToExceptions="true"
10-
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
6+
failOnDeprecation="true"
7+
failOnNotice="true"
8+
failOnWarning="true"
9+
failOnRisky="true"
10+
displayDetailsOnIncompleteTests="true"
11+
displayDetailsOnSkippedTests="true"
12+
displayDetailsOnTestsThatTriggerDeprecations="true"
13+
displayDetailsOnTestsThatTriggerErrors="true"
14+
displayDetailsOnTestsThatTriggerNotices="true"
15+
displayDetailsOnTestsThatTriggerWarnings="true"
16+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.5/phpunit.xsd">
1117
<testsuites>
1218
<testsuite name="Unit">
1319
<directory>test/unit</directory>
1420
</testsuite>
1521
</testsuites>
16-
<listeners>
17-
<listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener">
18-
<arguments>
19-
<array>
20-
<element key="slowThreshold">
21-
<integer>200</integer>
22-
</element>
23-
<element key="reportLength">
24-
<integer>20</integer>
25-
</element>
26-
</array>
27-
</arguments>
28-
</listener>
29-
</listeners>
3022
</phpunit>

src/BehatContexts/ContentSnippetsContext.php

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,62 +9,54 @@
99

1010
use Behat\Gherkin\Node\PyStringNode;
1111
use Behat\MinkExtension\Context\RawMinkContext;
12+
use Behat\Step\Given;
1213
use Ingenerator\ContentSnippets\Repository\ContentSnippetRepository;
14+
use PHPUnit\Framework\Assert;
15+
use function trim;
1316

1417
class ContentSnippetsContext extends RawMinkContext
1518
{
1619

17-
/**
18-
* @var \Ingenerator\ContentSnippets\Repository\ContentSnippetRepository
19-
*/
20-
protected $repo;
20+
protected ContentSnippetRepository $repo;
2121

2222
public function __construct(ContentSnippetRepository $repo)
2323
{
2424
$this->repo = $repo;
2525
}
2626

27-
/**
28-
* @Given /^the (?P<slug>[^ ]+) snippet has the following content:$/
29-
*/
30-
public function givenContent($slug, PyStringNode $content)
27+
#[Given('/^the (?P<slug>[^ ]+) snippet has the following content:$/')]
28+
public function givenContent(string $slug, PyStringNode $content): void
3129
{
3230
$snippet = $this->repo->load($slug);
3331
$snippet->setContent($content->getRaw());
3432
$this->repo->save($snippet);
3533
}
3634

37-
/**
38-
* @Given /^the (?P<slug>[^ ]+) snippet is empty$/
39-
*/
40-
public function givenEmpty($slug)
35+
#[Given('/^the (?P<slug>[^ ]+) snippet is empty$/')]
36+
public function givenEmpty(string $slug): void
4137
{
4238
$snippet = $this->repo->load($slug);
4339
$snippet->setContent(NULL);
4440
$this->repo->save($snippet);
4541
}
4642

47-
/**
48-
* @When /^I try to update the "(?P<display_name>[^"]+)" snippet with:$/
49-
*/
50-
public function tryToUpdateContent($display_name, PyStringNode $new_content)
43+
#[When('/^I try to update the "(?P<display_name>[^"]+)" snippet with:$/')]
44+
public function tryToUpdateContent(string $display_name, PyStringNode $new_content): void
5145
{
5246
$assert = $this->assertSession();
53-
$table = $assert->elementExists('css', '[data-content-snippets-list]');
47+
$table = $assert->elementExists('css', '[data-content-snippets-list]');
5448
$table->clickLink('Edit '.$display_name);
5549
$page = $this->getSession()->getPage();
5650
$page->fillField('Content', $new_content->getRaw());
5751
$page->pressButton('Save changes');
5852
}
5953

60-
/**
61-
* @Then /^the (?P<selector>.+?) element should have this exact HTML:$/
62-
*/
63-
public function assertElementExactHtml($selector, PyStringNode $expect)
54+
#[Then('/^the (?P<selector>.+?) element should have this exact HTML:$/')]
55+
public function assertElementExactHtml(string $selector, PyStringNode $expect): void
6456
{
6557
$element = $this->assertSession()->elementExists('css', $selector);
66-
$actual = \trim($element->getHtml());
67-
\PHPUnit\Framework\Assert::assertEquals($expect->getRaw(), $actual);
58+
$actual = trim($element->getHtml());
59+
Assert::assertEquals($expect->getRaw(), $actual);
6860
}
6961

7062
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace Ingenerator\ContentSnippets\ContentFilter;
4+
5+
use HTMLPurifier;
6+
use Ingenerator\ContentSnippets\ContentFilterResult;
7+
use Ingenerator\ContentSnippets\ContentSnippetContentFilter;
8+
use Ingenerator\ContentSnippets\Entity\ContentSnippet;
9+
10+
final class HtmlPurifierContentFilter implements ContentSnippetContentFilter
11+
{
12+
13+
public function __construct(
14+
private readonly HTMLPurifier $purifier
15+
) {
16+
}
17+
18+
public function filterContent(ContentSnippet $snippet, ?string $new_content): ContentFilterResult
19+
{
20+
if (ContentSnippet::isHtmlString($new_content)) {
21+
if ($snippet->allowsHtml()) {
22+
$cleaned = $this->cleanHtmlContent($new_content);
23+
24+
return new ContentFilterResult(
25+
cleaned_content: $cleaned,
26+
is_valid: TRUE,
27+
error_msg: NULL,
28+
was_cleaned: ($cleaned !== $new_content),
29+
);
30+
} else {
31+
return new ContentFilterResult(
32+
cleaned_content: $new_content,
33+
is_valid: FALSE,
34+
error_msg: self::MSG_NO_HTML,
35+
was_cleaned: FALSE
36+
);
37+
}
38+
} else {
39+
return new ContentFilterResult(
40+
cleaned_content: $new_content,
41+
is_valid: TRUE,
42+
error_msg: NULL,
43+
was_cleaned: FALSE
44+
);
45+
}
46+
}
47+
48+
protected function cleanHtmlContent(?string $new_content): ?string
49+
{
50+
return $this->purifier->purify($new_content);
51+
}
52+
}

src/ContentFilterResult.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Ingenerator\ContentSnippets;
4+
5+
readonly final class ContentFilterResult
6+
{
7+
8+
public function __construct(
9+
public ?string $cleaned_content,
10+
public bool $is_valid,
11+
public ?string $error_msg,
12+
public bool $was_cleaned,
13+
) {
14+
15+
}
16+
17+
}

src/ContentSnippetContentFilter.php

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,57 +9,10 @@
99

1010
use Ingenerator\ContentSnippets\Entity\ContentSnippet;
1111

12-
class ContentSnippetContentFilter
12+
interface ContentSnippetContentFilter
1313
{
14-
const MSG_NO_HTML = 'This snippet does not accept HTML content, please use plain text.';
14+
public const string MSG_NO_HTML = 'This snippet does not accept HTML content, please use plain text.';
1515

16-
/**
17-
* @var \HTMLPurifier
18-
*/
19-
private $purifier;
16+
public function filterContent(ContentSnippet $snippet, ?string $new_content): ContentFilterResult;
2017

21-
/**
22-
* ContentSnippetContentFilter constructor.
23-
*
24-
* @param \HTMLPurifier $purifier
25-
*/
26-
public function __construct(\HTMLPurifier $purifier)
27-
{
28-
$this->purifier = $purifier;
29-
}
30-
31-
public function filterContent(ContentSnippet $snippet, $new_content)
32-
{
33-
if (ContentSnippet::isHtmlString($new_content)) {
34-
if ($snippet->allowsHtml()) {
35-
$cleaned = $this->cleanHtmlContent($new_content);
36-
37-
return [
38-
'cleaned_content' => $cleaned,
39-
'is_valid' => TRUE,
40-
'error_msg' => NULL,
41-
'was_cleaned' => ($cleaned !== $new_content),
42-
];
43-
} else {
44-
return [
45-
'cleaned_content' => $new_content,
46-
'is_valid' => FALSE,
47-
'error_msg' => static::MSG_NO_HTML,
48-
'was_cleaned' => FALSE,
49-
];
50-
}
51-
} else {
52-
return [
53-
'cleaned_content' => $new_content,
54-
'is_valid' => TRUE,
55-
'error_msg' => NULL,
56-
'was_cleaned' => FALSE,
57-
];
58-
}
59-
}
60-
61-
protected function cleanHtmlContent($new_content)
62-
{
63-
return $this->purifier->purify($new_content);
64-
}
6518
}

0 commit comments

Comments
 (0)