Compare commits

...

20 Commits
2.3.1 ... 2

Author SHA1 Message Date
Maxime Rainville
ee49a440fb
Merge pull request #86 from creative-commoners/pulls/2/dispatch-ci
MNT Use gha-dispatch-ci
2023-03-23 12:16:16 +13:00
Steve Boyd
cb34aa869f MNT Use gha-dispatch-ci 2023-03-21 12:19:24 +13:00
Steve Boyd
5c8070044c Merge branch '2.4' into 2 2023-02-02 16:20:55 +13:00
Maxime Rainville
8e3f71afb7
Merge pull request #85 from creative-commoners/pulls/2.4/null-state
FIX Handle invalid json data
2023-01-26 13:15:33 +13:00
Steve Boyd
fe5e87598f FIX Handle invalid json data 2023-01-24 14:38:01 +13:00
Sabina Talipova
ffe1bc8fa2
Merge pull request #83 from creative-commoners/pulls/2/stop-depr
API Stop using deprecated API
2022-11-11 13:08:01 +13:00
Steve Boyd
132c00f122 API Stop using deprecated API 2022-11-03 18:11:29 +13:00
Steve Boyd
9b91b2de98 Merge branch '2.3' into 2 2022-08-02 19:06:18 +12:00
Steve Boyd
ecac4295af Merge branch '2.2' into 2.3 2022-08-02 19:05:52 +12:00
Guy Sartorelli
c4786cd955
Merge pull request #77 from creative-commoners/pulls/2.2/standardise-modules
MNT Standardise modules
2022-08-02 14:43:58 +12:00
Steve Boyd
ffe829485e MNT Standardise modules 2022-08-01 15:39:57 +12:00
Steve Boyd
fc63ebe8a9 Merge branch '2.3' into 2 2022-07-25 11:46:50 +12:00
Steve Boyd
8535a680ca Merge branch '2.2' into 2.3 2022-07-25 11:46:22 +12:00
Guy Sartorelli
8db444605a
MNT Fix linting issues (#76) 2022-07-18 13:18:35 +12:00
Guy Sartorelli
cefce74559
Merge pull request #75 from creative-commoners/pulls/2.0/module-standards
MNT Use GitHub Actions CI
2022-07-18 10:24:31 +12:00
Steve Boyd
a1e8643ea7 MNT Use GitHub Actions CI 2022-07-18 09:55:08 +12:00
Guy Sartorelli
0f5cb30743
Merge pull request #73 from creative-commoners/pulls/2/php81
ENH PHP 8.1 compatibility
2022-04-22 16:16:50 +12:00
Steve Boyd
7733cc7c95 Merge branch '2.3' into 2 2022-04-13 18:14:53 +12:00
Steve Boyd
5e2ef7e52c ENH PHP 8.1 compatibility 2022-04-13 17:40:59 +12:00
Ingo Schommer
e44774dbf0
Fixed composer require instruction 2020-07-27 17:30:49 +12:00
11 changed files with 107 additions and 127 deletions

11
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,11 @@
name: CI
on:
push:
pull_request:
workflow_dispatch:
jobs:
ci:
name: CI
uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1

16
.github/workflows/dispatch-ci.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Dispatch CI
on:
# At 2:30 PM UTC, only on Monday and Tuesday
schedule:
- cron: '30 14 * * 1,2'
jobs:
dispatch-ci:
name: Dispatch CI
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Dispatch CI
uses: silverstripe/gha-dispatch-ci@v1

17
.github/workflows/keepalive.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: Keepalive
on:
workflow_dispatch:
# The 4th of every month at 10:50am UTC
schedule:
- cron: '50 10 4 * *'
jobs:
keepalive:
name: Keepalive
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Keepalive
uses: silverstripe/gha-keepalive@v1

View File

@ -1,69 +0,0 @@
inherit: true
checks:
php:
verify_property_names: true
verify_argument_usable_as_reference: true
verify_access_scope_valid: true
useless_calls: true
use_statement_alias_conflict: true
variable_existence: true
unused_variables: true
unused_properties: true
unused_parameters: true
unused_methods: true
unreachable_code: true
too_many_arguments: true
sql_injection_vulnerabilities: true
simplify_boolean_return: true
side_effects_or_types: true
security_vulnerabilities: true
return_doc_comments: true
return_doc_comment_if_not_inferrable: true
require_scope_for_properties: true
require_scope_for_methods: true
require_php_tag_first: true
psr2_switch_declaration: true
psr2_class_declaration: true
property_assignments: true
prefer_while_loop_over_for_loop: true
precedence_mistakes: true
precedence_in_conditions: true
phpunit_assertions: true
php5_style_constructor: true
parse_doc_comments: true
parameter_non_unique: true
parameter_doc_comments: true
param_doc_comment_if_not_inferrable: true
optional_parameters_at_the_end: true
one_class_per_file: true
no_unnecessary_if: true
no_trailing_whitespace: true
no_property_on_interface: true
no_non_implemented_abstract_methods: true
no_error_suppression: true
no_duplicate_arguments: true
no_commented_out_code: true
newline_at_end_of_file: true
missing_arguments: true
method_calls_on_non_object: true
instanceof_class_exists: true
foreach_traversable: true
fix_line_ending: true
fix_doc_comments: true
duplication: true
deprecated_code_usage: true
deadlock_detection_in_loops: true
code_rating: true
closure_use_not_conflicting: true
catch_class_exists: true
blank_line_after_namespace_declaration: false
avoid_multiple_statements_on_same_line: true
avoid_duplicate_types: true
avoid_conflicting_incrementers: true
avoid_closing_tag: true
assignment_of_null_return: true
argument_type_checks: true
filter:
paths: [code/*, tests/*]

View File

@ -1,6 +1,6 @@
# Browser Test Session Module # Browser Test Session Module
[![Build Status](https://travis-ci.org/silverstripe-labs/silverstripe-testsession.svg)](https://travis-ci.org/silverstripe-labs/silverstripe-testsession) [![CI](https://github.com/silverstripe/silverstripe-testsession/actions/workflows/ci.yml/badge.svg)](https://github.com/silverstripe/silverstripe-testsession/actions/workflows/ci.yml)
## Overview ## Overview
@ -8,7 +8,7 @@
*It's completely possible to allow any user to become an admin, or do other nefarious things, if this is installed on a live site.* *It's completely possible to allow any user to become an admin, or do other nefarious things, if this is installed on a live site.*
This module starts a testing session in a browser, This module starts a testing session in a browser,
in order to test a SilverStripe application in a clean state. in order to test a Silverstripe application in a clean state.
Usually the session is started on a fresh database with only default records loaded. Usually the session is started on a fresh database with only default records loaded.
Further data can be loaded from YAML fixtures or database dumps. Further data can be loaded from YAML fixtures or database dumps.
@ -26,7 +26,7 @@ E.g. the silverstripe-behat-extension may use it through this module APIs,
allowing us to introduce some grey-box testing techniques. allowing us to introduce some grey-box testing techniques.
The module also serves as an initializer for the The module also serves as an initializer for the
[SilverStripe Behat Extension](https://github.com/silverstripe-labs/silverstripe-behat-extension/). [Silverstripe Behat Extension](https://github.com/silverstripe-labs/silverstripe-behat-extension/).
It is required for Behat because the Behat CLI test runner needs to persist It is required for Behat because the Behat CLI test runner needs to persist
test configuration just for the tested browser connection, test configuration just for the tested browser connection,
available on arbitary URL endpoints. For example, available on arbitary URL endpoints. For example,
@ -35,7 +35,7 @@ into a temporary database table for inspection by the CLI-based process.
## Setup ## Setup
Simply require the module in a SilverStripe webroot (3.0 or newer): Simply require the module in a Silverstripe webroot (3.0 or newer):
composer require --dev silverstripe/behat-extension composer require --dev silverstripe/behat-extension

View File

@ -19,10 +19,10 @@
"silverstripe/framework": "^4@dev", "silverstripe/framework": "^4@dev",
"silverstripe/vendor-plugin": "^1.3" "silverstripe/vendor-plugin": "^1.3"
}, },
"require-dev": {
"squizlabs/php_codesniffer": "^3.5"
},
"extra": { "extra": {
"branch-alias": {
"2.x-dev": "2.2.x-dev"
},
"expose": [ "expose": [
"client" "client"
] ]

View File

@ -2,6 +2,8 @@
<ruleset name="SilverStripe"> <ruleset name="SilverStripe">
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description> <description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
<file>src</file>
<!-- base rules are PSR-2 --> <!-- base rules are PSR-2 -->
<rule ref="PSR2" > <rule ref="PSR2" >
<!-- Current exclusions --> <!-- Current exclusions -->

View File

@ -104,7 +104,7 @@ class TestSessionController extends Controller
$id = null; $id = null;
} else { } else {
$generator = Injector::inst()->get(RandomGenerator::class); $generator = Injector::inst()->get(RandomGenerator::class);
$id = substr($generator->randomToken(), 0, 10); $id = substr($generator->randomToken() ?? '', 0, 10);
$this->getRequest()->getSession()->set('TestSessionId', $id); $this->getRequest()->getSession()->set('TestSessionId', $id);
} }
@ -113,7 +113,7 @@ class TestSessionController extends Controller
// Remove unnecessary items of form-specific data from being saved in the test session // Remove unnecessary items of form-specific data from being saved in the test session
$params = array_diff_key( $params = array_diff_key(
$params, $params ?? [],
array( array(
'action_set' => true, 'action_set' => true,
'action_start' => true, 'action_start' => true,
@ -170,7 +170,7 @@ class TestSessionController extends Controller
throw new LogicException("No test session in progress."); throw new LogicException("No test session in progress.");
} }
$newSessionStates = array_diff_key($request->getVars(), array('url' => true)); $newSessionStates = array_diff_key($request->getVars() ?? [], array('url' => true));
if (!$newSessionStates) { if (!$newSessionStates) {
throw new LogicException('No query parameters detected'); throw new LogicException('No query parameters detected');
} }
@ -288,7 +288,7 @@ class TestSessionController extends Controller
// Remove unnecessary items of form-specific data from being saved in the test session // Remove unnecessary items of form-specific data from being saved in the test session
$params = array_diff_key( $params = array_diff_key(
$params, $params ?? [],
array( array(
'action_set' => true, 'action_set' => true,
'action_start' => true, 'action_start' => true,
@ -399,7 +399,7 @@ class TestSessionController extends Controller
$path = BASE_PATH . '/' . $path; $path = BASE_PATH . '/' . $path;
} }
if ($path && file_exists($path)) { if ($path && file_exists($path ?? '')) {
$it = new FilesystemIterator($path); $it = new FilesystemIterator($path);
foreach ($it as $fileinfo) { foreach ($it as $fileinfo) {
if ($fileinfo->getExtension() != 'sql') { if ($fileinfo->getExtension() != 'sql') {

View File

@ -80,7 +80,6 @@ class TestSessionEnvironment
public function __construct($id = null) public function __construct($id = null)
{ {
$this->constructExtensions();
if ($id) { if ($id) {
$this->id = $id; $this->id = $id;
} }
@ -102,7 +101,7 @@ class TestSessionEnvironment
public function getFilePath() public function getFilePath()
{ {
if ($this->id) { if ($this->id) {
$path = Director::getAbsFile(sprintf($this->config()->get('test_state_id_file'), $this->id)); $path = Director::getAbsFile(sprintf($this->config()->get('test_state_id_file') ?? '', $this->id));
} else { } else {
$path = Director::getAbsFile($this->config()->get('test_state_file')); $path = Director::getAbsFile($this->config()->get('test_state_file'));
} }
@ -115,7 +114,7 @@ class TestSessionEnvironment
*/ */
public function isRunningTests() public function isRunningTests()
{ {
return (file_exists($this->getFilePath())); return (file_exists($this->getFilePath() ?? ''));
} }
/** /**
@ -161,7 +160,7 @@ class TestSessionEnvironment
// Convert to JSON and back so we can share the applyState() code between this and ->loadFromFile() // Convert to JSON and back so we can share the applyState() code between this and ->loadFromFile()
$json = json_encode($state, JSON_FORCE_OBJECT); $json = json_encode($state, JSON_FORCE_OBJECT);
$state = json_decode($json); $state = json_decode($json ?? '');
$this->applyState($state); $this->applyState($state);
@ -177,7 +176,7 @@ class TestSessionEnvironment
// Convert to JSON and back so we can share the appleState() code between this and ->loadFromFile() // Convert to JSON and back so we can share the appleState() code between this and ->loadFromFile()
$json = json_encode($state, JSON_FORCE_OBJECT); $json = json_encode($state, JSON_FORCE_OBJECT);
$state = json_decode($json); $state = json_decode($json ?? '');
$this->applyState($state); $this->applyState($state);
@ -192,7 +191,7 @@ class TestSessionEnvironment
{ {
// Ensure files backed up to assets dir // Ensure files backed up to assets dir
$backupFolder = $this->getAssetsBackupfolder(); $backupFolder = $this->getAssetsBackupfolder();
if (!is_dir($backupFolder)) { if (!is_dir($backupFolder ?? '')) {
Filesystem::makeFolder($backupFolder); Filesystem::makeFolder($backupFolder);
} }
$this->moveRecursive(ASSETS_PATH, $backupFolder, ['.htaccess', 'web.config', '.protected']); $this->moveRecursive(ASSETS_PATH, $backupFolder, ['.htaccess', 'web.config', '.protected']);
@ -206,7 +205,7 @@ class TestSessionEnvironment
{ {
// Ensure files backed up to assets dir // Ensure files backed up to assets dir
$backupFolder = $this->getAssetsBackupfolder(); $backupFolder = $this->getAssetsBackupfolder();
if (is_dir($backupFolder)) { if (is_dir($backupFolder ?? '')) {
// Move all files // Move all files
Filesystem::makeFolder(ASSETS_PATH); Filesystem::makeFolder(ASSETS_PATH);
$this->moveRecursive($backupFolder, ASSETS_PATH); $this->moveRecursive($backupFolder, ASSETS_PATH);
@ -224,12 +223,12 @@ class TestSessionEnvironment
protected function moveRecursive($src, $dest, $ignore = []) protected function moveRecursive($src, $dest, $ignore = [])
{ {
// If source is not a directory stop processing // If source is not a directory stop processing
if (!is_dir($src)) { if (!is_dir($src ?? '')) {
return; return;
} }
// If the destination directory does not exist create it // If the destination directory does not exist create it
if (!is_dir($dest) && !mkdir($dest)) { if (!is_dir($dest ?? '') && !mkdir($dest ?? '')) {
// If the destination directory could not be created stop processing // If the destination directory could not be created stop processing
return; return;
} }
@ -238,13 +237,13 @@ class TestSessionEnvironment
$iterator = new DirectoryIterator($src); $iterator = new DirectoryIterator($src);
foreach ($iterator as $file) { foreach ($iterator as $file) {
if ($file->isFile()) { if ($file->isFile()) {
if (!in_array($file->getFilename(), $ignore)) { if (!in_array($file->getFilename(), $ignore ?? [])) {
rename($file->getRealPath(), $dest . DIRECTORY_SEPARATOR . $file->getFilename()); rename($file->getRealPath() ?? '', $dest . DIRECTORY_SEPARATOR . $file->getFilename());
} }
} elseif (!$file->isDot() && $file->isDir()) { } elseif (!$file->isDot() && $file->isDir()) {
// If a dir is ignored, still move children but don't remove self // If a dir is ignored, still move children but don't remove self
$this->moveRecursive($file->getRealPath(), $dest . DIRECTORY_SEPARATOR . $file); $this->moveRecursive($file->getRealPath(), $dest . DIRECTORY_SEPARATOR . $file);
if (!in_array($file->getFilename(), $ignore)) { if (!in_array($file->getFilename(), $ignore ?? [])) {
Filesystem::removeFolder($file->getRealPath()); Filesystem::removeFolder($file->getRealPath());
} }
} }
@ -305,8 +304,8 @@ class TestSessionEnvironment
// Set existing one, assumes it already has been created // Set existing one, assumes it already has been created
$prefix = Environment::getEnv('SS_DATABASE_PREFIX') ?: 'ss_'; $prefix = Environment::getEnv('SS_DATABASE_PREFIX') ?: 'ss_';
$pattern = strtolower(sprintf('#^%stmpdb.*#', preg_quote($prefix, '#'))); $pattern = strtolower(sprintf('#^%stmpdb.*#', preg_quote($prefix ?? '', '#')));
if (!preg_match($pattern, $dbName)) { if (!preg_match($pattern ?? '', $dbName ?? '')) {
throw new InvalidArgumentException("Invalid database name format"); throw new InvalidArgumentException("Invalid database name format");
} }
@ -324,7 +323,7 @@ class TestSessionEnvironment
$mailer = (isset($state->mailer)) ? $state->mailer : null; $mailer = (isset($state->mailer)) ? $state->mailer : null;
if ($mailer) { if ($mailer) {
if (!class_exists($mailer) || !is_subclass_of($mailer, 'SilverStripe\\Control\\Email\\Mailer')) { if (!class_exists($mailer ?? '') || !is_subclass_of($mailer, 'SilverStripe\\Control\\Email\\Mailer')) {
throw new InvalidArgumentException(sprintf( throw new InvalidArgumentException(sprintf(
'Class "%s" is not a valid class, or subclass of Mailer', 'Class "%s" is not a valid class, or subclass of Mailer',
$mailer $mailer
@ -358,13 +357,13 @@ class TestSessionEnvironment
*/ */
public function importDatabase($path, $requireDefaultRecords = false) public function importDatabase($path, $requireDefaultRecords = false)
{ {
$sql = file_get_contents($path); $sql = file_get_contents($path ?? '');
// Split into individual query commands, removing comments // Split into individual query commands, removing comments
$sqlCmds = array_filter(preg_split( $sqlCmds = array_filter(preg_split(
'/;\n/', '/;\n/',
preg_replace(array('/^$\n/m', '/^(\/|#).*$\n/m'), '', $sql) preg_replace(array('/^$\n/m', '/^(\/|#).*$\n/m'), '', $sql ?? '') ?? ''
)); ) ?? []);
// Execute each query // Execute each query
foreach ($sqlCmds as $sqlCmd) { foreach ($sqlCmds as $sqlCmd) {
@ -401,7 +400,7 @@ class TestSessionEnvironment
$content = json_encode($state); $content = json_encode($state);
} }
$old = umask(0); $old = umask(0);
file_put_contents($this->getFilePath(), $content, LOCK_EX); file_put_contents($this->getFilePath() ?? '', $content, LOCK_EX);
umask($old); umask($old);
} }
@ -409,8 +408,8 @@ class TestSessionEnvironment
{ {
if ($this->isRunningTests()) { if ($this->isRunningTests()) {
try { try {
$contents = file_get_contents($this->getFilePath()); $contents = file_get_contents($this->getFilePath() ?? '');
$json = json_decode($contents); $json = json_decode($contents ?? '');
$this->applyState($json); $this->applyState($json);
} catch (Exception $e) { } catch (Exception $e) {
@ -428,8 +427,8 @@ class TestSessionEnvironment
{ {
$file = $this->getFilePath(); $file = $this->getFilePath();
if (file_exists($file)) { if (file_exists($file ?? '')) {
if (!unlink($file)) { if (!unlink($file ?? '')) {
throw new \Exception('Unable to remove the testsession state file, please remove it manually. File ' throw new \Exception('Unable to remove the testsession state file, please remove it manually. File '
. 'path: ' . $file); . 'path: ' . $file);
} }
@ -484,14 +483,14 @@ class TestSessionEnvironment
public function loadFixtureIntoDb($fixtureFile) public function loadFixtureIntoDb($fixtureFile)
{ {
$realFile = realpath(BASE_PATH . '/' . $fixtureFile); $realFile = realpath(BASE_PATH . '/' . $fixtureFile);
$baseDir = realpath(Director::baseFolder()); $baseDir = realpath(Director::baseFolder() ?? '');
if (!$realFile || !file_exists($realFile)) { if (!$realFile || !file_exists($realFile ?? '')) {
throw new LogicException("Fixture file doesn't exist"); throw new LogicException("Fixture file doesn't exist");
} elseif (substr($realFile, 0, strlen($baseDir)) != $baseDir) { } elseif (substr($realFile ?? '', 0, strlen($baseDir ?? '')) != $baseDir) {
throw new LogicException("Fixture file must be inside $baseDir"); throw new LogicException("Fixture file must be inside $baseDir");
} elseif (substr($realFile, -4) != '.yml') { } elseif (substr($realFile ?? '', -4) != '.yml') {
throw new LogicException("Fixture file must be a .yml file"); throw new LogicException("Fixture file must be a .yml file");
} elseif (!preg_match('/^([^\/.][^\/]+)\/tests\//', $fixtureFile)) { } elseif (!preg_match('/^([^\/.][^\/]+)\/tests\//', $fixtureFile ?? '')) {
throw new LogicException("Fixture file must be inside the tests subfolder of one of your modules."); throw new LogicException("Fixture file must be inside the tests subfolder of one of your modules.");
} }
@ -530,7 +529,10 @@ class TestSessionEnvironment
public function getState() public function getState()
{ {
$path = Director::getAbsFile($this->getFilePath()); $path = Director::getAbsFile($this->getFilePath());
return (file_exists($path)) ? json_decode(file_get_contents($path)) : new stdClass; if (file_exists($path ?? '')) {
return json_decode(file_get_contents($path)) ?: new stdClass;
}
return new stdClass;
} }
/** /**
@ -548,7 +550,8 @@ class TestSessionEnvironment
* *
* @param mixed $state * @param mixed $state
*/ */
public function connectToDatabase($state = null) { public function connectToDatabase($state = null)
{
if ($state == null) { if ($state == null) {
$state = $this->getState(); $state = $this->getState();
} }

View File

@ -69,7 +69,7 @@ class TestSessionHTTPMiddleware implements HTTPMiddleware
$mailer = $testState->mailer; $mailer = $testState->mailer;
Injector::inst()->registerService(new $mailer(), Mailer::class); Injector::inst()->registerService(new $mailer(), Mailer::class);
Email::config()->set("send_all_emails_to", null); Email::config()->set("send_all_emails_to", null);
Email::config()->update('admin_email', 'no-reply@example.com'); Email::config()->set('admin_email', 'no-reply@example.com');
} }
// Connect to the test session database // Connect to the test session database
@ -82,7 +82,7 @@ class TestSessionHTTPMiddleware implements HTTPMiddleware
// 'testsession.stubfile' state parameter. // 'testsession.stubfile' state parameter.
if (isset($testState->stubfile)) { if (isset($testState->stubfile)) {
$file = $testState->stubfile; $file = $testState->stubfile;
if (!Director::isLive() && $file && file_exists($file)) { if (!Director::isLive() && $file && file_exists($file ?? '')) {
include_once($file); include_once($file);
} }
} }

View File

@ -39,8 +39,8 @@ class TestSessionStubCodeWriter
$header = ''; $header = '';
// Create file incl. header if it doesn't exist // Create file incl. header if it doesn't exist
if (!file_exists($this->getFilePath())) { if (!file_exists($this->getFilePath() ?? '')) {
touch($this->getFilePath()); touch($this->getFilePath() ?? '');
if ($this->debug) { if ($this->debug) {
$header .= "<?php\n// Generated by " . $trace[1]['class'] . " on " . date('Y-m-d H:i:s') . "\n\n"; $header .= "<?php\n// Generated by " . $trace[1]['class'] . " on " . date('Y-m-d H:i:s') . "\n\n";
} else { } else {
@ -52,13 +52,13 @@ class TestSessionStubCodeWriter
if ($this->debug) { if ($this->debug) {
$header .= "// Added by " . $trace[1]['class'] . '::' . $trace[1]['function'] . "\n"; $header .= "// Added by " . $trace[1]['class'] . '::' . $trace[1]['function'] . "\n";
} }
file_put_contents($path, $header . $php . "\n", FILE_APPEND); file_put_contents($path ?? '', $header . $php . "\n", FILE_APPEND);
} }
public function reset() public function reset()
{ {
if (file_exists($this->getFilePath())) { if (file_exists($this->getFilePath() ?? '')) {
unlink($this->getFilePath()); unlink($this->getFilePath() ?? '');
} }
} }