From a97cb341e5eabe2c4711c381d21b8d4d5c450154 Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Mon, 1 Jun 2026 10:17:41 +0930 Subject: [PATCH 1/9] chore: support PHP 8.2 and up --- .github/workflows/ci.yml | 11 +- .gitignore | 1 + composer.json | 28 +- lib/BirthdayCalendarGenerator.php | 2 +- lib/Cli.php | 74 +--- lib/Component.php | 12 +- lib/Component/VAlarm.php | 2 +- lib/Component/VAvailability.php | 2 +- lib/Component/VCalendar.php | 2 +- lib/Component/VCard.php | 6 +- lib/Component/VEvent.php | 2 +- lib/Component/VFreeBusy.php | 2 +- lib/DateTimeParser.php | 4 +- lib/Document.php | 4 +- lib/FreeBusyData.php | 20 +- lib/FreeBusyGenerator.php | 18 +- lib/ITip/Broker.php | 12 +- lib/ITip/Message.php | 10 +- lib/Node.php | 4 +- lib/PHPUnitAssertions.php | 4 +- lib/Parameter.php | 87 +---- lib/Parser/Json.php | 33 +- lib/Parser/MimeDir.php | 33 +- lib/Parser/Parser.php | 8 +- lib/Parser/XML.php | 8 +- lib/Property.php | 45 +-- lib/Property/Binary.php | 6 +- lib/Property/Boolean.php | 4 +- lib/Property/FloatValue.php | 6 +- lib/Property/ICalendar/CalAddress.php | 4 +- lib/Property/ICalendar/DateTime.php | 10 +- lib/Property/ICalendar/Period.php | 6 +- lib/Property/ICalendar/Recur.php | 24 +- lib/Property/IntegerValue.php | 2 +- lib/Property/Text.php | 8 +- lib/Property/Time.php | 6 +- lib/Property/Uri.php | 12 +- lib/Property/UtcOffset.php | 10 +- lib/Property/VCard/DateAndOrTime.php | 10 +- lib/Recur/RDateIterator.php | 15 +- lib/Recur/RRuleIterator.php | 31 +- lib/Splitter/VCard.php | 17 +- .../FindFromTimezoneIdentifier.php | 2 +- lib/TimezoneGuesser/FindFromTimezoneMap.php | 4 +- lib/TimezoneGuesser/GuessFromLicEntry.php | 2 +- lib/TimezoneGuesser/GuessFromMsTzId.php | 2 +- lib/VCardConverter.php | 24 +- rector.php | 16 + tests/VObject/CliTest.php | 90 ++--- tests/VObject/Component/AvailableTest.php | 44 +-- tests/VObject/Component/VAvailabilityTest.php | 366 +++++++++--------- tests/VObject/ITip/BrokerTester.php | 2 +- ...ezoneInParseEventInfoWithoutMasterTest.php | 1 - tests/VObject/Property/TextTest.php | 16 +- tests/VObject/ReaderTest.php | 4 +- .../EventIterator/HandleRDateExpandTest.php | 2 +- tests/VObject/Splitter/ICalendarTest.php | 2 +- tests/VObject/Splitter/VCardTest.php | 2 +- tests/VObject/TimeZoneUtilTest.php | 14 +- tests/VObject/VCardConverterTest.php | 264 ++++++------- tests/phpunit.xml | 24 +- 61 files changed, 664 insertions(+), 822 deletions(-) create mode 100644 rector.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08c0b0a2d..2a7e69fb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,19 +12,22 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.0', '8.1', '8.2', '8.3', '8.4'] + php-versions: ['8.3', '8.4'] coverage: ['pcov'] code-style: ['no'] code-analysis: ['no'] + rector-check: ['no'] include: - - php-versions: '7.4' + - php-versions: '8.2' coverage: 'pcov' code-style: 'yes' code-analysis: 'yes' + rector-check: 'no' - php-versions: '8.5' coverage: 'pcov' code-style: 'no' code-analysis: 'yes' + rector-check: 'yes' steps: - name: Checkout uses: actions/checkout@v6 @@ -61,6 +64,10 @@ jobs: if: matrix.code-analysis == 'yes' run: composer phpstan + - name: Code Refactoring (rector) + if: matrix.rector-check == 'yes' + run: composer rector-check + - name: Test with phpunit run: vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover clover.xml diff --git a/.gitignore b/.gitignore index d0e5169ac..f38607123 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ vendor/ composer.lock tests/cov/ tests/temp +tests/.phpunit.cache tests/.phpunit.result.cache # Development stuff diff --git a/composer.json b/composer.json index 561906176..8ae9f914a 100644 --- a/composer.json +++ b/composer.json @@ -32,16 +32,20 @@ "homepage" : "http://sabre.io/vobject/", "license" : "BSD-3-Clause", "require" : { - "php" : "^7.4 || ^8.0", + "php" : "^8.2", "ext-mbstring" : "*", "ext-json" : "*", "sabre/xml" : "^3.0 || ^4.0" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "^3.94", - "phpunit/phpunit" : "^9.6", - "phpunit/php-invoker" : "^2.0 || ^3.1", - "phpstan/phpstan": "^2.1" + "friendsofphp/php-cs-fixer": "^3.95", + "phpstan/phpstan": "^2.2", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/extension-installer": "^1.4", + "phpunit/phpunit": "^11.5", + "phpunit/php-invoker" : "^5.0", + "rector/rector": "^2.4" }, "suggest" : { "hoa/bench" : "If you would like to run the benchmark scripts" @@ -96,6 +100,12 @@ "cs-fixer": [ "PHP_CS_FIXER_IGNORE_ENV=true php-cs-fixer fix" ], + "rector-check": [ + "rector process --dry-run" + ], + "rector-fix": [ + "rector process" + ], "phpunit": [ "phpunit --configuration tests/phpunit.xml" ], @@ -104,5 +114,13 @@ "composer cs-fixer", "composer phpunit" ] + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + }, + "platform": { + "php": "8.2" + } } } diff --git a/lib/BirthdayCalendarGenerator.php b/lib/BirthdayCalendarGenerator.php index 49793b0d4..6fb21f4f2 100644 --- a/lib/BirthdayCalendarGenerator.php +++ b/lib/BirthdayCalendarGenerator.php @@ -116,7 +116,7 @@ public function getResult(): VCalendar // Skip if we can't parse the BDAY value. try { $dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue()); - } catch (InvalidDataException $e) { + } catch (InvalidDataException) { continue; } diff --git a/lib/Cli.php b/lib/Cli.php index 69af6f470..38db47334 100644 --- a/lib/Cli.php +++ b/lib/Cli.php @@ -92,7 +92,7 @@ public function main(array $argv): int // @codeCoverageIgnoreEnd try { - list($options, $positional) = $this->parseArguments($argv); + [$options, $positional] = $this->parseArguments($argv); if (isset($options['q'])) { $this->quiet = true; @@ -110,27 +110,10 @@ public function main(array $argv): int return 0; case 'format': - switch ($value) { - // jcard/jcal documents - case 'jcard': - case 'jcal': - // specific document versions - case 'vcard21': - case 'vcard30': - case 'vcard40': - case 'icalendar20': - // specific formats - case 'json': - case 'mimedir': - // icalendar/vcard - case 'icalendar': - case 'vcard': - $this->format = $value; - break; - - default: - throw new \InvalidArgumentException('Unknown format: '.$value); - } + $this->format = match ($value) { + 'jcard', 'jcal', 'vcard21', 'vcard30', 'vcard40', 'icalendar20', 'json', 'mimedir', 'icalendar', 'vcard' => $value, + default => throw new \InvalidArgumentException('Unknown format: '.$value), + }; break; case 'pretty': $this->pretty = true; @@ -139,28 +122,11 @@ public function main(array $argv): int $this->forgiving = true; break; case 'inputformat': - switch ($value) { - // json formats - case 'jcard': - case 'jcal': - case 'json': - $this->inputFormat = 'json'; - break; - - // mimedir formats - case 'mimedir': - case 'icalendar': - case 'vcard': - case 'vcard21': - case 'vcard30': - case 'vcard40': - case 'icalendar20': - $this->inputFormat = 'mimedir'; - break; - - default: - throw new \InvalidArgumentException('Unknown format: '.$value); - } + $this->inputFormat = match ($value) { + 'jcard', 'jcal', 'json' => 'json', + 'mimedir', 'icalendar', 'vcard', 'vcard21', 'vcard30', 'vcard40', 'icalendar20' => 'mimedir', + default => throw new \InvalidArgumentException('Unknown format: '.$value), + }; break; default: throw new \InvalidArgumentException('Unknown option: '.$name); @@ -201,14 +167,14 @@ public function main(array $argv): int } if (null === $this->inputFormat) { - if ('.json' === substr($this->inputPath, -5)) { + if (str_ends_with($this->inputPath, '.json')) { $this->inputFormat = 'json'; } else { $this->inputFormat = 'mimedir'; } } if (null === $this->format) { - if ('.json' === substr($this->outputPath, -5)) { + if (str_ends_with($this->outputPath, '.json')) { $this->format = 'json'; } else { $this->format = 'mimedir'; @@ -224,7 +190,7 @@ public function main(array $argv): int $realCode = $returnCode; } } - } catch (EofException $e) { + } catch (EofException) { // end of file } catch (\Exception $e) { $this->log('Error: '.$e->getMessage(), 'red'); @@ -408,7 +374,7 @@ protected function convert(Component $vObj): int } fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions)); } else { - fwrite($this->stdout, $vObj->serialize()); + fwrite($this->stdout, (string) $vObj->serialize()); } return 0; @@ -538,7 +504,7 @@ protected function serializeProperty(Property $property): void $this->cWrite('red', ':'); if ($property instanceof Property\Binary) { - $this->cWrite('default', 'embedded binary stripped. ('.strlen($property->getValue()).' bytes)'); + $this->cWrite('default', 'embedded binary stripped. ('.strlen((string) $property->getValue()).' bytes)'); } else { $parts = $property->getParts(); $first1 = true; @@ -593,17 +559,17 @@ protected function parseArguments(array $argv): array $v = $argv[$ii]; - if ('--' === substr($v, 0, 2)) { + if (str_starts_with((string) $v, '--')) { // This is a long-form option. - $optionName = substr($v, 2); + $optionName = substr((string) $v, 2); $optionValue = true; if (strpos($optionName, '=')) { - list($optionName, $optionValue) = explode('=', $optionName); + [$optionName, $optionValue] = explode('=', $optionName); } $options[$optionName] = $optionValue; - } elseif ('-' === substr($v, 0, 1) && strlen($v) > 1) { + } elseif (str_starts_with((string) $v, '-') && strlen((string) $v) > 1) { // This is a short-form option. - foreach (str_split(substr($v, 1)) as $option) { + foreach (str_split(substr((string) $v, 1)) as $option) { $options[$option] = true; } } else { diff --git a/lib/Component.php b/lib/Component.php index 2a08a082e..b1a05d9aa 100644 --- a/lib/Component.php +++ b/lib/Component.php @@ -140,7 +140,7 @@ public function remove($item): void // If there's no dot in the name, it's an exact property name, // we can just wipe out all those properties. // - if (false === strpos($item, '.')) { + if (!str_contains($item, '.')) { unset($this->children[strtoupper($item)]); return; @@ -210,8 +210,8 @@ public function select(string $name): array { $group = null; $name = strtoupper($name); - if (false !== strpos($name, '.')) { - list($group, $name) = explode('.', $name, 2); + if (str_contains($name, '.')) { + [$group, $name] = explode('.', $name, 2); } if ('' === $name) { $name = null; @@ -228,9 +228,7 @@ public function select(string $name): array // more. return array_filter( $result, - function ($child) use ($group) { - return $child instanceof Property && (null !== $child->group ? strtoupper($child->group) : '') === $group; - } + fn ($child) => $child instanceof Property && (null !== $child->group ? strtoupper($child->group) : '') === $group ); } @@ -541,7 +539,7 @@ public function validate(int $options = 0): array $messages = []; foreach ($this->children() as $child) { - $name = strtoupper($child->name); + $name = strtoupper((string) $child->name); if (!isset($propertyCounters[$name])) { $propertyCounters[$name] = 1; } else { diff --git a/lib/Component/VAlarm.php b/lib/Component/VAlarm.php index 0cb6ab1de..0e7f05ce0 100644 --- a/lib/Component/VAlarm.php +++ b/lib/Component/VAlarm.php @@ -32,7 +32,7 @@ class VAlarm extends VObject\Component public function getEffectiveTriggerTime(): \DateTimeImmutable { $trigger = $this->TRIGGER; - if (!isset($trigger['VALUE']) || ($trigger['VALUE'] && 'DURATION' === strtoupper($trigger['VALUE']))) { + if (!isset($trigger['VALUE']) || ($trigger['VALUE'] && 'DURATION' === strtoupper((string) $trigger['VALUE']))) { $triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER); $related = (isset($trigger['RELATED']) && 'END' == strtoupper($trigger['RELATED'])) ? 'END' : 'START'; diff --git a/lib/Component/VAvailability.php b/lib/Component/VAvailability.php index ff6e9bd65..a4db47642 100644 --- a/lib/Component/VAvailability.php +++ b/lib/Component/VAvailability.php @@ -33,7 +33,7 @@ class VAvailability extends VObject\Component */ public function isInTimeRange(\DateTimeInterface $start, \DateTimeInterface $end): bool { - list($effectiveStart, $effectiveEnd) = $this->getEffectiveStartEnd(); + [$effectiveStart, $effectiveEnd] = $this->getEffectiveStartEnd(); return (is_null($effectiveStart) || $start < $effectiveEnd) diff --git a/lib/Component/VCalendar.php b/lib/Component/VCalendar.php index 5163c042a..a4dff82c9 100644 --- a/lib/Component/VCalendar.php +++ b/lib/Component/VCalendar.php @@ -327,7 +327,7 @@ public function expand(\DateTimeInterface $start, \DateTimeInterface $end, ?\Dat foreach ($recurringEvents as $events) { try { $it = new EventIterator($events, null, $timeZone); - } catch (NoInstancesException $e) { + } catch (NoInstancesException) { // This event is recurring, but it doesn't have a single // instance. We are skipping this event from the output // entirely. diff --git a/lib/Component/VCard.php b/lib/Component/VCard.php index 0dafb674b..1fe654712 100644 --- a/lib/Component/VCard.php +++ b/lib/Component/VCard.php @@ -423,10 +423,10 @@ public function getByType(string $propertyName, string $type) */ public function getByTypes(string $propertyName, array $types) { - $types = array_map('strtolower', $types); + $types = array_map(strtolower(...), $types); foreach ($this->select($propertyName) as $field) { if (isset($field['TYPE'])) { - $parts = array_map('strtolower', $field['TYPE']->getParts()); + $parts = array_map(strtolower(...), $field['TYPE']->getParts()); if (!array_diff($types, $parts) && !array_diff($parts, $types)) { return $field; @@ -493,7 +493,7 @@ public function xmlSerialize(Xml\Writer $writer): void foreach ($propertiesByGroup as $group => $properties) { if (!empty($group)) { $writer->startElement('group'); - $writer->writeAttribute('name', strtolower($group)); + $writer->writeAttribute('name', strtolower((string) $group)); } foreach ($properties as $property) { diff --git a/lib/Component/VEvent.php b/lib/Component/VEvent.php index 6ca196d66..c13081884 100644 --- a/lib/Component/VEvent.php +++ b/lib/Component/VEvent.php @@ -44,7 +44,7 @@ public function isInTimeRange(\DateTimeInterface $start, \DateTimeInterface $end if ($this->RRULE || $this->RDATE) { try { $it = new EventIterator($this, null, $start->getTimezone()); - } catch (NoInstancesException $e) { + } catch (NoInstancesException) { // If we've caught this exception, there are no instances // for the event that fall into the specified time-range. return false; diff --git a/lib/Component/VFreeBusy.php b/lib/Component/VFreeBusy.php index a9891c92c..623652ff9 100644 --- a/lib/Component/VFreeBusy.php +++ b/lib/Component/VFreeBusy.php @@ -41,7 +41,7 @@ public function isFree(\DateTimeInterface $start, \DatetimeInterface $end): bool // Every period is formatted as [start]/[end]. The start is an // absolute UTC time, the end may be an absolute UTC time, or // duration (relative) value. - list($busyStart, $busyEnd) = explode('/', $period); + [$busyStart, $busyEnd] = explode('/', $period); $busyStart = VObject\DateTimeParser::parse($busyStart); $busyEnd = VObject\DateTimeParser::parse($busyEnd); diff --git a/lib/DateTimeParser.php b/lib/DateTimeParser.php index 712a40faf..44e27c6fa 100644 --- a/lib/DateTimeParser.php +++ b/lib/DateTimeParser.php @@ -42,7 +42,7 @@ public static function parseDateTime(string $dt, ?\DateTimeZone $tz = null): \Da try { $date = new \DateTimeImmutable($matches[1].'-'.$matches[2].'-'.$matches[3].' '.$matches[4].':'.$matches[5].':'.$matches[6], $tz); - } catch (\Exception $e) { + } catch (\Exception) { throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: '.$dt); } @@ -69,7 +69,7 @@ public static function parseDate(string $date, ?\DateTimeZone $tz = null): \Date try { $date = new \DateTimeImmutable($matches[1].'-'.$matches[2].'-'.$matches[3], $tz); - } catch (\Exception $e) { + } catch (\Exception) { throw new InvalidDataException('The supplied iCalendar date value is incorrect: '.$date); } diff --git a/lib/Document.php b/lib/Document.php index aa3a404a7..c8fd99054 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -118,10 +118,10 @@ public function getDocumentType(): int public function create(string $name) { if (isset(static::$componentMap[strtoupper($name)])) { - return call_user_func_array([$this, 'createComponent'], func_get_args()); + return call_user_func_array($this->createComponent(...), func_get_args()); } - return call_user_func_array([$this, 'createProperty'], func_get_args()); + return call_user_func_array($this->createProperty(...), func_get_args()); } /** diff --git a/lib/FreeBusyData.php b/lib/FreeBusyData.php index 405a5886d..6cdf2b937 100644 --- a/lib/FreeBusyData.php +++ b/lib/FreeBusyData.php @@ -11,25 +11,19 @@ */ class FreeBusyData { - /** - * Start timestamp. - */ - protected int $start; - - /** - * End timestamp. - */ - protected int $end; - /** * A list of free-busy times. */ protected array $data; - public function __construct(int $start, int $end) + public function __construct(/** + * Start timestamp. + */ + protected int $start, /** + * End timestamp. + */ + protected int $end) { - $this->start = $start; - $this->end = $end; $this->data = []; $this->data[] = [ diff --git a/lib/FreeBusyGenerator.php b/lib/FreeBusyGenerator.php index 424826034..3bcc842e3 100644 --- a/lib/FreeBusyGenerator.php +++ b/lib/FreeBusyGenerator.php @@ -217,7 +217,7 @@ function ($a, $b) { $new = []; foreach ($old as $vavail) { - list($compStart, $compEnd) = $vavail->getEffectiveStartEnd(); + [$compStart, $compEnd] = $vavail->getEffectiveStartEnd(); // We don't care about date-times that are earlier or later than the // start and end of the freebusy report, so this gets normalized @@ -237,7 +237,7 @@ function ($a, $b) { // Going through our existing list of components to see if there's // a higher priority component that already fully covers this one. foreach ($new as $higherVavail) { - list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd(); + [$higherStart, $higherEnd] = $higherVavail->getEffectiveStartEnd(); if ( (is_null($higherStart) || $higherStart < $compStart) && (is_null($higherEnd) || $higherEnd > $compEnd) @@ -259,7 +259,7 @@ function ($a, $b) { // priority components to override the lower ones. foreach (array_reverse($new) as $vavail) { $busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE'; - list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd(); + [$vavailStart, $vavailEnd] = $vavail->getEffectiveStartEnd(); // Making the component size no larger than the requested free-busy // report range. @@ -281,7 +281,7 @@ function ($a, $b) { // Looping over the AVAILABLE components. if (isset($vavail->AVAILABLE)) { foreach ($vavail->AVAILABLE as $available) { - list($availStart, $availEnd) = $available->getEffectiveStartEnd(); + [$availStart, $availEnd] = $available->getEffectiveStartEnd(); $fbData->add( $availStart->getTimeStamp(), $availEnd->getTimeStamp(), @@ -362,7 +362,7 @@ protected function calculateBusy(FreeBusyData $fbData, array $objects): void if ($component->RRULE) { try { $iterator = new EventIterator($object, (string) $component->UID, $this->timeZone); - } catch (NoInstancesException $e) { + } catch (NoInstancesException) { // This event is recurring, but it doesn't have a single // instance. We are skipping this event from the output // entirely. @@ -437,12 +437,12 @@ protected function calculateBusy(FreeBusyData $fbData, array $objects): void continue; } - $values = explode(',', $freebusy); + $values = explode(',', (string) $freebusy); foreach ($values as $value) { - list($startTime, $endTime) = explode('/', $value); + [$startTime, $endTime] = explode('/', $value); $startTime = DateTimeParser::parseDateTime($startTime); - if ('P' === substr($endTime, 0, 1) || '-P' === substr($endTime, 0, 2)) { + if (str_starts_with($endTime, 'P') || str_starts_with($endTime, '-P')) { $duration = DateTimeParser::parseDuration($endTime); $endTime = clone $startTime; $endTime = $endTime->add($duration); @@ -507,7 +507,7 @@ protected function generateFreeBusyCalendar(FreeBusyData $fbData): VCalendar $vfreebusy->add($dtstamp); foreach ($fbData->getData() as $busyTime) { - $busyType = strtoupper($busyTime['type']); + $busyType = strtoupper((string) $busyTime['type']); // Ignoring all the FREE parts, because those are already assumed. if ('FREE' === $busyType) { diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index 9bcdcc2a2..f36b32787 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -354,7 +354,7 @@ protected function processMessageReply(Message $itipMessage, ?VCalendar $existin $instances[$recurId] = $attendee['PARTSTAT']->getValue(); if (isset($vevent->{'REQUEST-STATUS'})) { $requestStatus = $vevent->{'REQUEST-STATUS'}->getValue(); - list($requestStatus) = explode(';', $requestStatus); + [$requestStatus] = explode(';', $requestStatus); } } @@ -754,7 +754,7 @@ protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, // EXDATE $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']); // Treat is as a DATE field - if (strlen($instance['id']) <= 8) { + if (strlen((string) $instance['id']) <= 8) { $event->add('DTSTART', $dt, ['VALUE' => 'DATE']); } else { $event->add('DTSTART', $dt); @@ -766,7 +766,7 @@ protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, if ('master' !== $instance['id']) { $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']); // Treat is as a DATE field - if (strlen($instance['id']) <= 8) { + if (strlen((string) $instance['id']) <= 8) { $event->add('RECURRENCE-ID', $dt, ['VALUE' => 'DATE']); } else { $event->add('RECURRENCE-ID', $dt); @@ -858,7 +858,7 @@ protected function parseEventInfo(VCalendar $calendar): array $organizer = $vevent->ORGANIZER->getNormalizedValue(); $organizerName = $vevent->ORGANIZER['CN'] ?? null; } else { - if (strtoupper($organizer) !== strtoupper($vevent->ORGANIZER->getNormalizedValue())) { + if (strtoupper($organizer) !== strtoupper((string) $vevent->ORGANIZER->getNormalizedValue())) { throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.'); } } @@ -896,7 +896,7 @@ protected function parseEventInfo(VCalendar $calendar): array sort($rrule); } if (isset($vevent->STATUS)) { - $status = strtoupper($vevent->STATUS->getValue()); + $status = strtoupper((string) $vevent->STATUS->getValue()); } $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master'; @@ -914,7 +914,7 @@ protected function parseEventInfo(VCalendar $calendar): array foreach ($vevent->ATTENDEE as $attendee) { if ($this->scheduleAgentServerRules && isset($attendee['SCHEDULE-AGENT']) - && 'CLIENT' === strtoupper($attendee['SCHEDULE-AGENT']->getValue()) + && 'CLIENT' === strtoupper((string) $attendee['SCHEDULE-AGENT']->getValue()) ) { continue; } diff --git a/lib/ITip/Message.php b/lib/ITip/Message.php index 6f59eebc2..505e07f25 100644 --- a/lib/ITip/Message.php +++ b/lib/ITip/Message.php @@ -32,12 +32,12 @@ class Message * Contains the ITip method, which is something like REQUEST, REPLY or * CANCEL. */ - public ?string $method; + public ?string $method = null; /** * The current sequence number for the event. */ - public ?int $sequence; + public ?int $sequence = null; /** * The senders' email address. @@ -52,7 +52,7 @@ class Message * The name of the sender. This is often populated from a CN parameter from * either the ORGANIZER or ATTENDEE, depending on the message. */ - public ?string $senderName; + public ?string $senderName = null; /** * The recipient's email address. @@ -63,7 +63,7 @@ class Message * The name of the recipient. This is usually populated with the CN * parameter from the ATTENDEE or ORGANIZER property, if it's available. */ - public ?string $recipientName; + public ?string $recipientName = null; /** * After the message has been delivered, this should contain a string such @@ -108,7 +108,7 @@ public function getScheduleStatus() if (!$this->scheduleStatus) { return false; } - list($scheduleStatus) = explode(';', $this->scheduleStatus); + [$scheduleStatus] = explode(';', $this->scheduleStatus); return $scheduleStatus; } diff --git a/lib/Node.php b/lib/Node.php index 0dbe7cfad..47889a2ab 100644 --- a/lib/Node.php +++ b/lib/Node.php @@ -42,7 +42,7 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js /** * Reference to the parent object, if this is not the top object. */ - public ?Node $parent; + public ?Node $parent = null; /** * Iterator override. @@ -52,7 +52,7 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js /** * The root document. */ - protected ?Component $root; + protected ?Component $root = null; /** * Serializes the node into a mimedir format. diff --git a/lib/PHPUnitAssertions.php b/lib/PHPUnitAssertions.php index 1976c3afd..a201379be 100644 --- a/lib/PHPUnitAssertions.php +++ b/lib/PHPUnitAssertions.php @@ -55,13 +55,13 @@ public function assertVObjectEqualsVObject($expected, $actual, string $message = $actual = $getObj($actual)->serialize(); // Finding wildcards in expected. - preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', $expected, $matches, PREG_SET_ORDER); + preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', (string) $expected, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $actual = preg_replace( '|^'.preg_quote($match[1], '|').':(.*)\r$|m', $match[1].':**ANY**'."\r", - $actual + (string) $actual ); } diff --git a/lib/Parameter.php b/lib/Parameter.php index 9952d3d2b..5b0804ec5 100644 --- a/lib/Parameter.php +++ b/lib/Parameter.php @@ -16,7 +16,7 @@ * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ -class Parameter extends Node +class Parameter extends Node implements \Stringable { /** * Parameter name. @@ -74,83 +74,12 @@ public function __construct(Document $root, ?string $name, $value = null) */ public static function guessParameterNameByValue(string $value): string { - switch (strtoupper($value)) { - // Encodings - case '7-BIT': - case 'QUOTED-PRINTABLE': - case 'BASE64': - $name = 'ENCODING'; - break; - - // Common types - case 'WORK': - case 'HOME': - case 'PREF': - // Delivery Label Type - case 'DOM': - case 'INTL': - case 'POSTAL': - case 'PARCEL': - // Telephone types - case 'VOICE': - case 'FAX': - case 'MSG': - case 'CELL': - case 'PAGER': - case 'BBS': - case 'MODEM': - case 'CAR': - case 'ISDN': - case 'VIDEO': - // EMAIL types (lol) - case 'AOL': - case 'APPLELINK': - case 'ATTMAIL': - case 'CIS': - case 'EWORLD': - case 'INTERNET': - case 'IBMMAIL': - case 'MCIMAIL': - case 'POWERSHARE': - case 'PRODIGY': - case 'TLX': - case 'X400': - // Photo / Logo format types - case 'GIF': - case 'CGM': - case 'WMF': - case 'BMP': - case 'DIB': - case 'PICT': - case 'TIFF': - case 'PDF': - case 'PS': - case 'JPEG': - case 'MPEG': - case 'MPEG2': - case 'AVI': - case 'QTIME': - // Sound Digital Audio Type - case 'WAVE': - case 'PCM': - case 'AIFF': - // Key types - case 'X509': - case 'PGP': - $name = 'TYPE'; - break; - - // Value types - case 'INLINE': - case 'URL': - case 'CONTENT-ID': - case 'CID': - $name = 'VALUE'; - break; - - default: - $name = ''; - } + $name = match (strtoupper($value)) { + '7-BIT', 'QUOTED-PRINTABLE', 'BASE64' => 'ENCODING', + 'WORK', 'HOME', 'PREF', 'DOM', 'INTL', 'POSTAL', 'PARCEL', 'VOICE', 'FAX', 'MSG', 'CELL', 'PAGER', 'BBS', 'MODEM', 'CAR', 'ISDN', 'VIDEO', 'AOL', 'APPLELINK', 'ATTMAIL', 'CIS', 'EWORLD', 'INTERNET', 'IBMMAIL', 'MCIMAIL', 'POWERSHARE', 'PRODIGY', 'TLX', 'X400', 'GIF', 'CGM', 'WMF', 'BMP', 'DIB', 'PICT', 'TIFF', 'PDF', 'PS', 'JPEG', 'MPEG', 'MPEG2', 'AVI', 'QTIME', 'WAVE', 'PCM', 'AIFF', 'X509', 'PGP' => 'TYPE', + 'INLINE', 'URL', 'CONTENT-ID', 'CID' => 'VALUE', + default => '', + }; return $name; } @@ -234,7 +163,7 @@ public function has(string $value): bool { return in_array( strtolower($value), - array_map('strtolower', (array) $this->value) + array_map(strtolower(...), (array) $this->value) ); } diff --git a/lib/Parser/Json.php b/lib/Parser/Json.php index 6403af4e9..45f985f68 100644 --- a/lib/Parser/Json.php +++ b/lib/Parser/Json.php @@ -27,12 +27,12 @@ class Json extends Parser /** * The input data. */ - protected ?array $input; + protected ?array $input = null; /** * Root component. */ - protected ?Document $root; + protected ?Document $root = null; /** * This method starts the parsing process. @@ -61,16 +61,11 @@ public function parse($input = null, int $options = 0): ?Document $this->options = $options; } - switch ($this->input[0]) { - case 'vcalendar': - $this->root = new VCalendar([], false); - break; - case 'vcard': - $this->root = new VCard([], false); - break; - default: - throw new ParseException('The root component must either be a vcalendar, or a vcard'); - } + $this->root = match ($this->input[0]) { + 'vcalendar' => new VCalendar([], false), + 'vcard' => new VCard([], false), + default => throw new ParseException('The root component must either be a vcalendar, or a vcard'), + }; foreach ($this->input[1] as $prop) { $this->root->add($this->parseProperty($prop)); } @@ -97,17 +92,13 @@ public function parseComponent(array $jComp): Component $self = $this; $properties = array_map( - function ($jProp) use ($self) { - return $self->parseProperty($jProp); - }, + $self->parseProperty(...), $jComp[1] ); if (isset($jComp[2])) { $components = array_map( - function ($jComp) use ($self) { - return $self->parseComponent($jComp); - }, + $self->parseComponent(...), $jComp[2] ); } else { @@ -134,7 +125,7 @@ public function parseProperty(array $jProp): Property $valueType, ) = $jProp; - $propertyName = strtoupper($propertyName); + $propertyName = strtoupper((string) $propertyName); // This is the default class we would be using if we didn't know the // value type. We're using this value later in this function. @@ -144,7 +135,7 @@ public function parseProperty(array $jProp): Property $value = array_slice($jProp, 3); - $valueType = strtoupper($valueType); + $valueType = strtoupper((string) $valueType); if (isset($parameters['group'])) { $propertyName = $parameters['group'].'.'.$propertyName; @@ -165,7 +156,7 @@ public function parseProperty(array $jProp): Property // If the value type we received (e.g.: TEXT) was not the default value // type for the given property (e.g.: BDAY), we need to add a VALUE= // parameter. - if ($defaultPropertyClass !== get_class($prop)) { + if ($defaultPropertyClass !== $prop::class) { $prop['VALUE'] = $valueType; } diff --git a/lib/Parser/MimeDir.php b/lib/Parser/MimeDir.php index 7718dba04..b71ae2634 100644 --- a/lib/Parser/MimeDir.php +++ b/lib/Parser/MimeDir.php @@ -41,7 +41,7 @@ class MimeDir extends Parser /** * Root component. */ - protected ?Document $root; + protected ?Document $root = null; /** * By default, all input will be assumed to be UTF-8. @@ -155,23 +155,18 @@ protected function parseDocument(): void // BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF). // It's 0xEF 0xBB 0xBF in UTF-8 hex. - if (3 <= strlen($line) + if (3 <= strlen((string) $line) && 0xEF === ord($line[0]) && 0xBB === ord($line[1]) && 0xBF === ord($line[2])) { - $line = \substr($line, 3); + $line = \substr((string) $line, 3); } - switch (strtoupper($line)) { - case 'BEGIN:VCALENDAR': - $class = VCalendar::$componentMap['VCALENDAR']; - break; - case 'BEGIN:VCARD': - $class = VCard::$componentMap['VCARD']; - break; - default: - throw new ParseException('This parser only supports VCARD and VCALENDAR files'); - } + $class = match (strtoupper((string) $line)) { + 'BEGIN:VCALENDAR' => VCalendar::$componentMap['VCALENDAR'], + 'BEGIN:VCARD' => VCard::$componentMap['VCARD'], + default => throw new ParseException('This parser only supports VCARD and VCALENDAR files'), + }; $this->root = new $class([], false); @@ -179,10 +174,10 @@ protected function parseDocument(): void // Reading until we hit END: try { $line = $this->readLine(); - } catch (EofException $oEx) { + } catch (EofException) { $line = 'END:'.$this->root->name; } - if ('END:' === strtoupper(\substr($line, 0, 4))) { + if ('END:' === strtoupper(\substr((string) $line, 0, 4))) { break; } $result = $this->parseLine($line); @@ -191,7 +186,7 @@ protected function parseDocument(): void } } - $name = strtoupper(\substr($line, 4)); + $name = strtoupper(\substr((string) $line, 4)); if ($name !== $this->root->name) { throw new ParseException('Invalid MimeDir file. expected: "END:'.$this->root->name.'" got: "END:'.$name.'"'); } @@ -220,7 +215,7 @@ protected function parseLine(string $line) while (true) { // Reading until we hit END: $line = $this->readLine(); - if ('END:' === strtoupper(\substr($line, 0, 4))) { + if ('END:' === strtoupper(\substr((string) $line, 0, 4))) { break; } $result = $this->parseLine($line); @@ -229,7 +224,7 @@ protected function parseLine(string $line) } } - $name = strtoupper(\substr($line, 4)); + $name = strtoupper(\substr((string) $line, 4)); if ($name !== $component->name) { throw new ParseException('Invalid MimeDir file. expected: "END:'.$component->name.'" got: "END:'.$name.'"'); } @@ -691,7 +686,7 @@ private function extractQuotedPrintableValue(): string // missing a whitespace. So if 'forgiving' is turned on, we will take // those as well. if ($this->options & self::OPTION_FORGIVING) { - while ('=' === \substr($value, -1) && $this->lineBuffer) { + while (str_ends_with($value, '=') && $this->lineBuffer) { // Reading the line $this->readLine(); // Grabbing the raw form diff --git a/lib/Parser/Parser.php b/lib/Parser/Parser.php index 29921d182..c5126445a 100644 --- a/lib/Parser/Parser.php +++ b/lib/Parser/Parser.php @@ -30,11 +30,6 @@ abstract class Parser */ public const OPTION_IGNORE_INVALID_LINES = 2; - /** - * Bitmask of parser options. - */ - protected int $options; - /** * Creates the parser. * @@ -42,12 +37,11 @@ abstract class Parser * * @param int $options any parser options (OPTION constants) */ - public function __construct($input = null, int $options = 0) + public function __construct($input = null, protected int $options = 0) { if (!is_null($input)) { $this->setInput($input); } - $this->options = $options; } /** diff --git a/lib/Parser/XML.php b/lib/Parser/XML.php index 9a5100684..77a229733 100644 --- a/lib/Parser/XML.php +++ b/lib/Parser/XML.php @@ -28,17 +28,17 @@ class XML extends Parser /** * The input data. */ - protected ?array $input; + protected ?array $input = null; /** * A pointer/reference to the input. */ - private ?array $pointer; + private ?array $pointer = null; /** * Document, root component. */ - protected ?Document $root; + protected ?Document $root = null; /** * Creates the parser. @@ -146,7 +146,7 @@ protected function parseVCardComponents(Component $parentComponent): void protected function parseProperties(Component $parentComponent, string $propertyNamePrefix = ''): void { foreach ($this->pointer ?: [] as $xmlProperty) { - list($namespace, $tagName) = SabreXml\Service::parseClarkNotation($xmlProperty['name']); + [$namespace, $tagName] = SabreXml\Service::parseClarkNotation($xmlProperty['name']); $propertyName = $tagName; $propertyValue = []; diff --git a/lib/Property.php b/lib/Property.php index 56b571e51..e369b5239 100644 --- a/lib/Property.php +++ b/lib/Property.php @@ -14,27 +14,8 @@ * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ -abstract class Property extends Node +abstract class Property extends Node implements \Stringable { - /** - * The root document. - */ - public ?Component $root; - - /** - * Property name. - * - * This will contain a string such as DTSTART, SUMMARY, FN. - */ - public ?string $name; - - /** - * Property group. - * - * This is only used in vcards - */ - public ?string $group; - /** * List of parameters. */ @@ -75,13 +56,13 @@ abstract class Property extends Node * @param array $parameters List of parameters * @param string|null $group The vcard property group */ - public function __construct(Component $root, ?string $name, $value = null, array $parameters = [], ?string $group = null, ?int $lineIndex = null, ?string $lineString = null) + public function __construct(public ?Component $root, /** + * Property name. + * + * This will contain a string such as DTSTART, SUMMARY, FN. + */ + public ?string $name, $value = null, array $parameters = [], public ?string $group = null, ?int $lineIndex = null, ?string $lineString = null) { - $this->name = $name; - $this->group = $group; - - $this->root = $root; - foreach ($parameters as $k => $v) { $this->add($k, $v); } @@ -242,7 +223,7 @@ public function serialize(): string ); // remove single space after last CRLF - return \substr($str, 0, -1); + return \substr((string) $str, 0, -1); } /** @@ -282,7 +263,7 @@ public function jsonSerialize(): array if ('VALUE' === $parameter->name) { continue; } - $parameters[strtolower($parameter->name)] = $parameter->jsonSerialize(); + $parameters[strtolower((string) $parameter->name)] = $parameter->jsonSerialize(); } // In jCard, we need to encode the property-group as a separate 'group' // parameter. @@ -292,7 +273,7 @@ public function jsonSerialize(): array return array_merge( [ - strtolower($this->name), + strtolower((string) $this->name), (object) $parameters, strtolower($this->getValueType()), ], @@ -329,13 +310,13 @@ public function xmlSerialize(Xml\Writer $writer): void $parameters[] = $parameter; } - $writer->startElement(strtolower($this->name)); + $writer->startElement(strtolower((string) $this->name)); if (!empty($parameters)) { $writer->startElement('parameters'); foreach ($parameters as $parameter) { - $writer->startElement(strtolower($parameter->name)); + $writer->startElement(strtolower((string) $parameter->name)); $writer->write($parameter); $writer->endElement(); } @@ -520,7 +501,7 @@ public function validate(int $options = 0): array } // Checking if the property name does not contain any invalid bytes. - if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) { + if (!preg_match('/^([A-Z0-9-]+)$/', (string) $this->name)) { $warnings[] = [ 'level' => $options & self::REPAIR ? 1 : 3, 'message' => 'The property name: '.$this->name.' contains invalid characters. Only A-Z, 0-9 and - are allowed', diff --git a/lib/Property/Binary.php b/lib/Property/Binary.php index 25cda8d25..124794c9d 100644 --- a/lib/Property/Binary.php +++ b/lib/Property/Binary.php @@ -62,7 +62,7 @@ public function setRawMimeDirValue(string $val): void */ public function getRawMimeDirValue(): string { - return base64_encode($this->value); + return base64_encode((string) $this->value); } /** @@ -83,7 +83,7 @@ public function getValueType(): string */ public function getJsonValue(): array { - return [base64_encode($this->getValue())]; + return [base64_encode((string) $this->getValue())]; } /** @@ -93,7 +93,7 @@ public function getJsonValue(): array */ public function setJsonValue(array $value): void { - $value = array_map('base64_decode', $value); + $value = array_map(base64_decode(...), $value); parent::setJsonValue($value); } } diff --git a/lib/Property/Boolean.php b/lib/Property/Boolean.php index cdc3408aa..f33fbafdc 100644 --- a/lib/Property/Boolean.php +++ b/lib/Property/Boolean.php @@ -56,9 +56,7 @@ public function getValueType(): string public function setXmlValue(array $value): void { $value = array_map( - function ($value) { - return 'true' === $value; - }, + fn ($value) => 'true' === $value, $value ); parent::setXmlValue($value); diff --git a/lib/Property/FloatValue.php b/lib/Property/FloatValue.php index 7a36e0976..32b02a972 100644 --- a/lib/Property/FloatValue.php +++ b/lib/Property/FloatValue.php @@ -67,7 +67,7 @@ public function getValueType(): string */ public function getJsonValue(): array { - $val = array_map('floatval', $this->getParts()); + $val = array_map(floatval(...), $this->getParts()); // Special-casing the GEO property. // @@ -86,7 +86,7 @@ public function getJsonValue(): array */ public function setXmlValue(array $value): void { - $value = array_map('floatval', $value); + $value = array_map(floatval(...), $value); parent::setXmlValue($value); } @@ -101,7 +101,7 @@ protected function xmlSerializeValue(Xml\Writer $writer): void // See: // http://tools.ietf.org/html/rfc6321#section-3.4.1.2 if ('GEO' === $this->name) { - $value = array_map('floatval', $this->getParts()); + $value = array_map(floatval(...), $this->getParts()); $writer->writeElement('latitude', $value[0]); $writer->writeElement('longitude', $value[1]); diff --git a/lib/Property/ICalendar/CalAddress.php b/lib/Property/ICalendar/CalAddress.php index 01969e87d..a2b13eee7 100644 --- a/lib/Property/ICalendar/CalAddress.php +++ b/lib/Property/ICalendar/CalAddress.php @@ -43,10 +43,10 @@ public function getValueType(): string public function getNormalizedValue(): string { $input = $this->getValue(); - if (!strpos($input, ':')) { + if (!strpos((string) $input, ':')) { return $input; } - list($schema, $everythingElse) = explode(':', $input, 2); + [$schema, $everythingElse] = explode(':', (string) $input, 2); $schema = strtolower($schema); if ('mailto' === $schema) { $everythingElse = strtolower($everythingElse); diff --git a/lib/Property/ICalendar/DateTime.php b/lib/Property/ICalendar/DateTime.php index 4f2ff4261..e4c876563 100644 --- a/lib/Property/ICalendar/DateTime.php +++ b/lib/Property/ICalendar/DateTime.php @@ -111,7 +111,7 @@ public function isFloating(): bool !$this->hasTime() || ( !isset($this['TZID']) - && false === strpos($this->getValue(), 'Z') + && !str_contains((string) $this->getValue(), 'Z') ); } @@ -286,9 +286,7 @@ public function setJsonValue(array $value): void // those. $this->setValue( array_map( - function ($item) { - return strtr($item, [':' => '', '-' => '']); - }, + fn ($item) => strtr($item, [':' => '', '-' => '']), $value ) ); @@ -306,7 +304,7 @@ function ($item) { public function offsetSet($offset, $value): void { parent::offsetSet($offset, $value); - if ('VALUE' !== strtoupper($offset)) { + if ('VALUE' !== strtoupper((string) $offset)) { return; } @@ -347,7 +345,7 @@ public function validate(int $options = 0): array DateTimeParser::parseDateTime($value); break; } - } catch (InvalidDataException $e) { + } catch (InvalidDataException) { $messages[] = [ 'level' => 3, 'message' => 'The supplied value ('.$value.') is not a correct '.$valueType, diff --git a/lib/Property/ICalendar/Period.php b/lib/Property/ICalendar/Period.php index 7632cb4d1..15291e805 100644 --- a/lib/Property/ICalendar/Period.php +++ b/lib/Property/ICalendar/Period.php @@ -64,9 +64,7 @@ public function getValueType(): string public function setJsonValue(array $value): void { $value = array_map( - function ($item) { - return strtr(implode('/', $item), [':' => '', '-' => '']); - }, + fn ($item) => strtr(implode('/', $item), [':' => '', '-' => '']), $value ); parent::setJsonValue($value); @@ -83,7 +81,7 @@ public function getJsonValue(): array { $return = []; foreach ($this->getParts() as $item) { - list($start, $end) = explode('/', $item, 2); + [$start, $end] = explode('/', (string) $item, 2); $start = DateTimeParser::parseDateTime($start); diff --git a/lib/Property/ICalendar/Recur.php b/lib/Property/ICalendar/Recur.php index 10081673e..66bb54970 100644 --- a/lib/Property/ICalendar/Recur.php +++ b/lib/Property/ICalendar/Recur.php @@ -29,7 +29,7 @@ class Recur extends Property /** * Reference to the parent object, if this is not the top object. */ - public ?Node $parent; + public ?Node $parent = null; /** * Updates the current value. @@ -52,17 +52,17 @@ public function setValue($value): void $v = strtoupper($v); // The value had multiple sub-values - if (false !== strpos($v, ',')) { + if (str_contains($v, ',')) { $v = explode(',', $v); } - if (0 === strcmp($k, 'until')) { + if (0 === strcmp((string) $k, 'until')) { $v = strtr($v, [':' => '', '-' => '']); } } elseif (is_array($v)) { - $v = array_map('strtoupper', $v); + $v = array_map(strtoupper(...), $v); } - $newVal[strtoupper($k)] = $v; + $newVal[strtoupper((string) $k)] = $v; } $this->value = $newVal; } elseif (is_string($value)) { @@ -151,13 +151,13 @@ public function getJsonValue(): array { $values = []; foreach ($this->getParts() as $k => $v) { - if (0 === strcmp($k, 'UNTIL')) { + if (0 === strcmp((string) $k, 'UNTIL')) { $date = new DateTime($this->root, null, $v); - $values[strtolower($k)] = $date->getJsonValue()[0]; - } elseif (0 === strcmp($k, 'COUNT')) { - $values[strtolower($k)] = intval($v); + $values[strtolower((string) $k)] = $date->getJsonValue()[0]; + } elseif (0 === strcmp((string) $k, 'COUNT')) { + $values[strtolower((string) $k)] = intval($v); } else { - $values[strtolower($k)] = $v; + $values[strtolower((string) $k)] = $v; } } @@ -196,10 +196,10 @@ public static function stringToArray(string $value): array throw new InvalidDataException('The supplied iCalendar RRULE part is incorrect: '.$part); } - list($partName, $partValue) = $parts; + [$partName, $partValue] = $parts; // The value itself had multiple values.. - if (false !== strpos($partValue, ',')) { + if (str_contains($partValue, ',')) { $partValue = explode(',', $partValue); } $newValue[$partName] = $partValue; diff --git a/lib/Property/IntegerValue.php b/lib/Property/IntegerValue.php index f1ae55d17..1cd7273cc 100644 --- a/lib/Property/IntegerValue.php +++ b/lib/Property/IntegerValue.php @@ -62,7 +62,7 @@ public function getJsonValue(): array */ public function setXmlValue(array $value): void { - $value = array_map('intval', $value); + $value = array_map(intval(...), $value); parent::setXmlValue($value); } } diff --git a/lib/Property/Text.php b/lib/Property/Text.php index 363c3fa4f..7b044346c 100644 --- a/lib/Property/Text.php +++ b/lib/Property/Text.php @@ -213,7 +213,7 @@ public function serialize(): string // If the resulting value contains a \n, we must encode it as // quoted-printable. - if (false !== \strpos($val, "\n")) { + if (str_contains((string) $val, "\n")) { $str .= ';ENCODING=QUOTED-PRINTABLE:'; $lastLine = $str; $out = ''; @@ -222,13 +222,13 @@ public function serialize(): string // encode newlines for us. Specifically, the \r\n sequence must in // vcards be encoded as =0D=OA and we must insert soft-newlines // every 75 bytes. - for ($ii = 0; $ii < \strlen($val); ++$ii) { + for ($ii = 0; $ii < \strlen((string) $val); ++$ii) { $ord = \ord($val[$ii]); // These characters are encoded as themselves. if ($ord >= 32 && $ord <= 126) { $lastLine .= $val[$ii]; } else { - $lastLine .= '='.\strtoupper(\bin2hex($val[$ii])); + $lastLine .= '='.\strtoupper(\bin2hex((string) $val[$ii])); } if (\strlen($lastLine) >= 75) { // Soft line break @@ -255,7 +255,7 @@ public function serialize(): string ); // remove single space after last CRLF - return \substr($str, 0, -1); + return \substr((string) $str, 0, -1); } /** diff --git a/lib/Property/Time.php b/lib/Property/Time.php index ed314fc0d..9c4016c64 100644 --- a/lib/Property/Time.php +++ b/lib/Property/Time.php @@ -104,7 +104,7 @@ public function getJsonValue(): array $timeStr .= 'Z'; } else { $timeStr .= - preg_replace('/([0-9]{2})([0-9]{2})$/', '$1:$2', $parts['timezone']); + preg_replace('/([0-9]{2})([0-9]{2})$/', '$1:$2', (string) $parts['timezone']); } } @@ -118,9 +118,7 @@ public function getJsonValue(): array public function setXmlValue(array $value): void { $value = array_map( - function ($value) { - return str_replace(':', '', $value); - }, + fn ($value) => str_replace(':', '', $value), $value ); parent::setXmlValue($value); diff --git a/lib/Property/Uri.php b/lib/Property/Uri.php index 228b7090f..ec98f7416 100644 --- a/lib/Property/Uri.php +++ b/lib/Property/Uri.php @@ -74,14 +74,10 @@ public function setRawMimeDirValue(string $val): void $matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $newVal = ''; foreach ($matches as $match) { - switch ($match) { - case '\:': - $newVal .= ':'; - break; - default: - $newVal .= $match; - break; - } + match ($match) { + '\:' => $newVal .= ':', + default => $newVal .= $match, + }; } $this->value = $newVal; } else { diff --git a/lib/Property/UtcOffset.php b/lib/Property/UtcOffset.php index 09aab0151..52d81f1f6 100644 --- a/lib/Property/UtcOffset.php +++ b/lib/Property/UtcOffset.php @@ -42,9 +42,7 @@ public function getValueType(): string public function setJsonValue(array $value): void { $value = array_map( - function ($value) { - return str_replace(':', '', $value); - }, + fn ($value) => str_replace(':', '', $value), $value ); parent::setJsonValue($value); @@ -58,10 +56,8 @@ function ($value) { public function getJsonValue(): array { return array_map( - function ($value) { - return substr($value, 0, -2).':'. - substr($value, -2); - }, + fn ($value) => substr((string) $value, 0, -2).':'. + substr((string) $value, -2), parent::getJsonValue() ); } diff --git a/lib/Property/VCard/DateAndOrTime.php b/lib/Property/VCard/DateAndOrTime.php index e87bf1b3a..7aa88a628 100644 --- a/lib/Property/VCard/DateAndOrTime.php +++ b/lib/Property/VCard/DateAndOrTime.php @@ -232,14 +232,10 @@ protected function xmlSerializeValue(Xml\Writer $writer): void $value = ''; // $d = defined - $d = function ($part) use ($parts): bool { - return !is_null($parts[$part]); - }; + $d = (fn ($part): bool => !is_null($parts[$part])); // $r = read - $r = function ($part) use ($parts) { - return $parts[$part]; - }; + $r = (fn ($part) => $parts[$part]); // From the Relax NG Schema. // @@ -341,7 +337,7 @@ public function validate(int $options = 0): array try { DateTimeParser::parseVCardDateTime($value); - } catch (InvalidDataException $e) { + } catch (InvalidDataException) { $messages[] = [ 'level' => 3, 'message' => 'The supplied value ('.$value.') is not a correct DATE-AND-OR-TIME property', diff --git a/lib/Recur/RDateIterator.php b/lib/Recur/RDateIterator.php index 4ad688f9d..b9bd0beff 100644 --- a/lib/Recur/RDateIterator.php +++ b/lib/Recur/RDateIterator.php @@ -26,9 +26,13 @@ class RDateIterator implements \Iterator * * @param string|array $rrule */ - public function __construct($rrule, \DateTimeInterface $start) + public function __construct($rrule, /** + * The reference start date/time for the rrule. + * + * All calculations are based on this initial date. + */ + protected \DateTimeInterface $startDate) { - $this->startDate = $start; $this->parseRDate($rrule); $this->currentDate = clone $this->startDate; } @@ -117,13 +121,6 @@ public function fastForward(\DateTimeInterface $dt): void } } - /** - * The reference start date/time for the rrule. - * - * All calculations are based on this initial date. - */ - protected \DateTimeInterface $startDate; - /** * The date of the current iteration. You can get this by calling * ->current(). diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 87e185b1c..b260b2d04 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -37,9 +37,13 @@ class RRuleIterator implements \Iterator * * @throws InvalidDataException */ - public function __construct($rrule, \DateTimeInterface $start) + public function __construct($rrule, /** + * The reference start date/time for the rrule. + * + * All calculations are based on this initial date. + */ + protected \DateTimeInterface $startDate) { - $this->startDate = $start; $this->parseRRule($rrule); $this->currentDate = clone $this->startDate; } @@ -146,13 +150,6 @@ public function fastForward(\DateTimeInterface $dt): void } } - /** - * The reference start date/time for the rrule. - * - * All calculations are based on this initial date. - */ - protected \DateTimeInterface $startDate; - /** * The date of the current iteration. You can get this by calling * ->current(). @@ -752,10 +749,10 @@ protected function parseRRule($rrule): void } foreach ($rrule as $key => $value) { - $key = strtoupper($key); + $key = strtoupper((string) $key); switch ($key) { case 'FREQ': - $value = strtolower($value); + $value = strtolower((string) $value); if (!in_array( $value, ['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'] @@ -806,7 +803,7 @@ protected function parseRRule($rrule): void case 'BYDAY': $value = (array) $value; foreach ($value as $part) { - if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) { + if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', (string) $part)) { throw new InvalidDataException('Invalid part in BYDAY clause: '.$part); } } @@ -849,7 +846,7 @@ protected function parseRRule($rrule): void break; case 'WKST': - $this->weekStart = strtoupper($value); + $this->weekStart = strtoupper((string) $value); break; default: @@ -889,7 +886,7 @@ protected function getMonthlyOccurrences(): array // that point and add it to the results. if ($this->byDay) { foreach ($this->byDay as $day) { - $dayName = $this->dayNames[$this->dayMap[substr($day, -2)]]; + $dayName = $this->dayNames[$this->dayMap[substr((string) $day, -2)]]; // Dayname will be something like 'wednesday'. Now we need to find // all wednesdays in this month. @@ -910,8 +907,8 @@ protected function getMonthlyOccurrences(): array // So now we have 'all wednesdays' for month. It is however // possible that the user only really wanted the 1st, 2nd or last // wednesday. - if (strlen($day) > 2) { - $offset = (int) substr($day, 0, -2); + if (strlen((string) $day) > 2) { + $offset = (int) substr((string) $day, 0, -2); if ($offset > 0) { // It is possible that the day does not exist, such as a @@ -1015,7 +1012,7 @@ protected function getDays(): array // The day may be preceded with a positive (+n) or // negative (-n) integer. However, this does not make // sense in 'weekly' so we ignore it here. - $recurrenceDays[] = $this->dayMap[substr($byDay, -2)]; + $recurrenceDays[] = $this->dayMap[substr((string) $byDay, -2)]; } return $recurrenceDays; diff --git a/lib/Splitter/VCard.php b/lib/Splitter/VCard.php index 40775a335..a802dbe75 100644 --- a/lib/Splitter/VCard.php +++ b/lib/Splitter/VCard.php @@ -22,13 +22,6 @@ */ class VCard implements SplitterInterface { - /** - * File handle. - * - * @var resource - */ - protected $input; - /** * Persistent parser. */ @@ -42,10 +35,12 @@ class VCard implements SplitterInterface * @param resource $input * @param int $options parser options, see the OPTIONS constants */ - public function __construct($input, int $options = 0) + public function __construct(/** + * File handle. + */ + protected $input, int $options = 0) { - $this->input = $input; - $this->parser = new MimeDir($input, $options); + $this->parser = new MimeDir($this->input, $options); } /** @@ -64,7 +59,7 @@ public function getNext(): ?Component if (!$object instanceof Component\VCard) { throw new VObject\ParseException('The supplied input contained non-VCARD data.'); } - } catch (VObject\EofException $e) { + } catch (VObject\EofException) { return null; } diff --git a/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php b/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php index ba84d45eb..7547d574a 100644 --- a/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php +++ b/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php @@ -46,7 +46,7 @@ public function find(string $tzid, ?bool $failIfUncertain = false): ?\DateTimeZo ) { return new \DateTimeZone($tzid); } - } catch (\Exception $e) { + } catch (\Exception) { } return null; diff --git a/lib/TimezoneGuesser/FindFromTimezoneMap.php b/lib/TimezoneGuesser/FindFromTimezoneMap.php index f203937bc..5beb5450d 100644 --- a/lib/TimezoneGuesser/FindFromTimezoneMap.php +++ b/lib/TimezoneGuesser/FindFromTimezoneMap.php @@ -27,7 +27,7 @@ public function find(string $tzid, ?bool $failIfUncertain = false): ?\DateTimeZo if ($this->hasTzInMap($tzid)) { try { return new \DateTimeZone($this->getTzFromMap($tzid)); - } catch (\Exception $e) { + } catch (\Exception) { return null; } } @@ -43,7 +43,7 @@ public function find(string $tzid, ?bool $failIfUncertain = false): ?\DateTimeZo if ($this->hasTzInMap($tzidAlternate)) { try { return new \DateTimeZone($this->getTzFromMap($tzidAlternate)); - } catch (\Exception $e) { + } catch (\Exception) { return null; } } diff --git a/lib/TimezoneGuesser/GuessFromLicEntry.php b/lib/TimezoneGuesser/GuessFromLicEntry.php index 486b33796..7212117f7 100644 --- a/lib/TimezoneGuesser/GuessFromLicEntry.php +++ b/lib/TimezoneGuesser/GuessFromLicEntry.php @@ -23,7 +23,7 @@ public function guess(VTimeZone $vtimezone, ?bool $failIfUncertain = false): ?\D // Libical generators may specify strings like // "SystemV/EST5EDT". For those we must remove the // SystemV part. - if ('SystemV/' === substr($lic, 0, 8)) { + if (str_starts_with($lic, 'SystemV/')) { $lic = substr($lic, 8); } diff --git a/lib/TimezoneGuesser/GuessFromMsTzId.php b/lib/TimezoneGuesser/GuessFromMsTzId.php index 392333cb0..82060bd27 100644 --- a/lib/TimezoneGuesser/GuessFromMsTzId.php +++ b/lib/TimezoneGuesser/GuessFromMsTzId.php @@ -105,7 +105,7 @@ public function guess(VTimeZone $vtimezone, ?bool $failIfUncertain = false): ?\D $cdoId = (int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue(); // 2 can mean both Europe/Lisbon and Europe/Sarajevo. - if (2 === $cdoId && false !== strpos((string) $vtimezone->TZID, 'Sarajevo')) { + if (2 === $cdoId && str_contains((string) $vtimezone->TZID, 'Sarajevo')) { return new \DateTimeZone('Europe/Sarajevo'); } diff --git a/lib/VCardConverter.php b/lib/VCardConverter.php index ff8fe3f81..c1d41bdb3 100644 --- a/lib/VCardConverter.php +++ b/lib/VCardConverter.php @@ -127,7 +127,7 @@ protected function convertProperty(Component\VCard $input, Component\VCard $outp $output->add('ITEM'.$x.'.X-ABLABEL', '_$!!$_'); } } elseif ('KIND' === $property->name) { - switch (strtolower($property->getValue())) { + switch (strtolower((string) $property->getValue())) { case 'org': // vCard 3.0 does not have an equivalent to KIND:ORG, // but apple has an extension that means the same @@ -171,12 +171,12 @@ protected function convertProperty(Component\VCard $input, Component\VCard $outp } switch ($property->name) { case 'X-ABSHOWAS': - if ('COMPANY' === strtoupper($property->getValue())) { + if ('COMPANY' === strtoupper((string) $property->getValue())) { $newProperty = $output->createProperty('KIND', 'ORG'); } break; case 'X-ADDRESSBOOKSERVER-KIND': - if ('GROUP' === strtoupper($property->getValue())) { + if ('GROUP' === strtoupper((string) $property->getValue())) { $newProperty = $output->createProperty('KIND', 'GROUP'); } break; @@ -274,10 +274,10 @@ protected function convertBinaryToUri(Component\VCard $output, Binary $newProper $newTypes = []; foreach ($parameters['TYPE']->getParts() as $typePart) { if (in_array( - strtoupper($typePart), + strtoupper((string) $typePart), ['JPEG', 'PNG', 'GIF'] )) { - $mimeType = 'image/'.strtolower($typePart); + $mimeType = 'image/'.strtolower((string) $typePart); } else { $newTypes[] = $typePart; } @@ -292,7 +292,7 @@ protected function convertBinaryToUri(Component\VCard $output, Binary $newProper } } - $newProperty->setValue('data:'.$mimeType.';base64,'.base64_encode($value)); + $newProperty->setValue('data:'.$mimeType.';base64,'.base64_encode((string) $value)); return $newProperty; } @@ -313,7 +313,7 @@ protected function convertUriToBinary(Component\VCard $output, Uri $newProperty) $value = $newProperty->getValue(); // Only converting data: uris - if ('data:' !== substr($value, 0, 5)) { + if (!str_starts_with((string) $value, 'data:')) { return $newProperty; } @@ -325,12 +325,12 @@ protected function convertUriToBinary(Component\VCard $output, Uri $newProperty) 'BINARY' ); - $mimeType = substr($value, 5, strpos($value, ',') - 5); + $mimeType = substr((string) $value, 5, strpos((string) $value, ',') - 5); if (strpos($mimeType, ';')) { $mimeType = substr($mimeType, 0, strpos($mimeType, ';')); - $newProperty->setValue(base64_decode(substr($value, strpos($value, ',') + 1))); + $newProperty->setValue(base64_decode(substr((string) $value, strpos((string) $value, ',') + 1))); } else { - $newProperty->setValue(substr($value, strpos($value, ',') + 1)); + $newProperty->setValue(substr((string) $value, strpos((string) $value, ',') + 1)); } unset($value); @@ -367,7 +367,7 @@ protected function convertParameters40(Property $newProperty, array $parameters) // that's now PREF=1. case 'TYPE': foreach ($param->getParts() as $paramPart) { - if ('PREF' === strtoupper($paramPart)) { + if ('PREF' === strtoupper((string) $paramPart)) { $newProperty->add('PREF', '1'); } else { $newProperty->add($param->name, $paramPart); @@ -402,7 +402,7 @@ protected function convertParameters30(Property $newProperty, array $parameters) case 'ENCODING': // This value only existed in vCard 2.1, and should be // removed for anything else. - if ('QUOTED-PRINTABLE' !== strtoupper($param->getValue())) { + if ('QUOTED-PRINTABLE' !== strtoupper((string) $param->getValue())) { $newProperty->add($param->name, $param->getParts()); } break; diff --git a/rector.php b/rector.php new file mode 100644 index 000000000..201fc8090 --- /dev/null +++ b/rector.php @@ -0,0 +1,16 @@ +withPaths([ + __DIR__.'/lib', + __DIR__.'/tests', + ]) + // uncomment to reach your current PHP version + ->withPhpSets(false, true) + ->withTypeCoverageLevel(0) + ->withDeadCodeLevel(0) + ->withCodeQualityLevel(0); diff --git a/tests/VObject/CliTest.php b/tests/VObject/CliTest.php index c57d25226..6801593b2 100644 --- a/tests/VObject/CliTest.php +++ b/tests/VObject/CliTest.php @@ -302,14 +302,14 @@ public function testVCard3040(): void { $inputStream = fopen('php://memory', 'r+'); - fwrite($inputStream, <<cli->stdin = $inputStream; @@ -341,14 +341,14 @@ public function testVCard4030(): void { $inputStream = fopen('php://memory', 'r+'); - fwrite($inputStream, <<cli->stdin = $inputStream; @@ -380,14 +380,14 @@ public function testVCard4021(): void { $inputStream = fopen('php://memory', 'r+'); - fwrite($inputStream, <<cli->stdin = $inputStream; @@ -402,15 +402,15 @@ public function testValidate(): void { $inputStream = fopen('php://memory', 'r+'); - fwrite($inputStream, <<cli->stdin = $inputStream; @@ -426,12 +426,12 @@ public function testValidateFail(): void { $inputStream = fopen('php://memory', 'r+'); - fwrite($inputStream, <<cli->stdin = $inputStream; @@ -466,12 +466,12 @@ public function testRepair(): void { $inputStream = fopen('php://memory', 'r+'); - fwrite($inputStream, <<cli->stdin = $inputStream; diff --git a/tests/VObject/Component/AvailableTest.php b/tests/VObject/Component/AvailableTest.php index 98202ffcb..68c9d90f0 100644 --- a/tests/VObject/Component/AvailableTest.php +++ b/tests/VObject/Component/AvailableTest.php @@ -13,26 +13,26 @@ class AvailableTest extends TestCase { public function testAvailableComponent(): void { - $vcal = <<AVAILABLE); } public function testGetEffectiveStartEnd(): void { - $vcal = <<VAVAILABILITY); @@ -27,14 +27,14 @@ public function testVAvailabilityComponent(): void public function testGetEffectiveStartEnd(): void { - $vcal = <<VAVAILABILITY->AVAILABLE); @@ -241,91 +241,91 @@ public function testRFCxxxSection3Part1AvailablePropRequired(): void { // UID, DTSTAMP and DTSTART are present. self::assertIsValid(Reader::read( - <<validate(); if ($validationResult) { - $messages = array_map(function ($item) { return $item['message']; }, $validationResult); + $messages = array_map(fn ($item) => $item['message'], $validationResult); $this->fail('Failed to assert that the supplied document is a valid document. Validation messages: '.implode(', ', $messages)); } self::assertEmpty($document->validate()); @@ -425,17 +425,17 @@ protected function assertIsNotValid(VObject\Document $document): void protected function template(array $properties) { return $this->_template( - <<_template( - <<sender = $mainComponent->ATTENDEE->getValue(); $message->senderName = isset($mainComponent->ATTENDEE['CN']) ? $mainComponent->ATTENDEE['CN']->getValue() : null; $message->recipient = $mainComponent->ORGANIZER->getValue(); - $message->recipientName = isset($mainComponent->ORGANIZER['CN']) ? $mainComponent->ORGANIZER['CN'] : null; + $message->recipientName = $mainComponent->ORGANIZER['CN'] ?? null; } $broker = new Broker(); diff --git a/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php b/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php index e3f18f15e..c0afe4578 100644 --- a/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php +++ b/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php @@ -70,7 +70,6 @@ public function testTimezoneInParseEventInfoWithoutMaster(): void $broker = new Broker(); $reflectionMethod = new \ReflectionMethod($broker, 'parseEventInfo'); - $reflectionMethod->setAccessible(true); $data = $reflectionMethod->invoke($broker, $calendar); self::assertInstanceOf('DateTimeZone', $data['timezone']); self::assertEquals('Europe/Minsk', $data['timezone']->getName()); diff --git a/tests/VObject/Property/TextTest.php b/tests/VObject/Property/TextTest.php index 0fa320bfe..aadbd4141 100644 --- a/tests/VObject/Property/TextTest.php +++ b/tests/VObject/Property/TextTest.php @@ -66,14 +66,14 @@ public function testSerializeQuotedPrintableFold(): void public function testValidateMinimumPropValue(): void { - $vcard = <<validate()); diff --git a/tests/VObject/ReaderTest.php b/tests/VObject/ReaderTest.php index 645ed1813..7b6c953fd 100644 --- a/tests/VObject/ReaderTest.php +++ b/tests/VObject/ReaderTest.php @@ -318,7 +318,7 @@ public function testReadForgiving(): void $caught = false; try { Reader::read(implode("\r\n", $data)); - } catch (ParseException $e) { + } catch (ParseException) { $caught = true; } @@ -348,7 +348,7 @@ public function testReadWithInvalidLine(): void $caught = false; try { Reader::read(implode("\r\n", $data)); - } catch (ParseException $e) { + } catch (ParseException) { $caught = true; } diff --git a/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php b/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php index 4f434dd2a..aca783d3c 100644 --- a/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php +++ b/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php @@ -51,7 +51,7 @@ public function testExpand(): void new \DateTimeImmutable('2015-10-20', $utc), ]; - $result = array_map(function ($ev) {return $ev->DTSTART->getDateTime(); }, $result); + $result = array_map(fn ($ev) => $ev->DTSTART->getDateTime(), $result); self::assertEquals($expected, $result); } } diff --git a/tests/VObject/Splitter/ICalendarTest.php b/tests/VObject/Splitter/ICalendarTest.php index f5428e937..0688e378b 100644 --- a/tests/VObject/Splitter/ICalendarTest.php +++ b/tests/VObject/Splitter/ICalendarTest.php @@ -169,7 +169,7 @@ public function testICalendarImportEventWithoutUID(): void if ($messages) { $messages = array_map( - function ($item) { return $item['message']; }, + fn ($item) => $item['message'], $messages ); $this->fail('Validation errors: '.implode("\n", $messages)); diff --git a/tests/VObject/Splitter/VCardTest.php b/tests/VObject/Splitter/VCardTest.php index 7ebb07b95..b4d6b0a22 100644 --- a/tests/VObject/Splitter/VCardTest.php +++ b/tests/VObject/Splitter/VCardTest.php @@ -10,7 +10,7 @@ class VCardTest extends TestCase public function createStream($data) { $stream = fopen('php://memory', 'r+'); - fwrite($stream, $data); + fwrite($stream, (string) $data); rewind($stream); return $stream; diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index 462218e9b..bc45de8e5 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -20,7 +20,7 @@ public function testCorrectTZ(string $timezoneName): void $tz = new \DateTimeZone($timezoneName); self::assertInstanceOf('DateTimeZone', $tz); } catch (\Exception $e) { - if (false !== strpos($e->getMessage(), 'Unknown or bad timezone')) { + if (str_contains($e->getMessage(), 'Unknown or bad timezone')) { $this->markTestSkipped($timezoneName.' is not (yet) supported in this PHP version. Update pecl/timezonedb'); } else { throw $e; @@ -39,9 +39,7 @@ public function getMapping(): array // PHPUNit requires an array of arrays return array_map( - function ($value) { - return [$value]; - }, + fn ($value) => [$value], $map ); } @@ -202,9 +200,7 @@ public function getPHPTimeZoneIdentifiers(): array { // PHPUNit requires an array of arrays return array_map( - function ($value) { - return [$value]; - }, + fn ($value) => [$value], \DateTimeZone::listIdentifiers() ); } @@ -213,9 +209,7 @@ public function getPHPTimeZoneBCIdentifiers(): array { // PHPUNit requires an array of arrays return array_map( - function ($value) { - return [$value]; - }, + fn ($value) => [$value], include __DIR__.'/../../lib/timezonedata/php-bc.php' ); } diff --git a/tests/VObject/VCardConverterTest.php b/tests/VObject/VCardConverterTest.php index bb52be8a9..cff394fe5 100644 --- a/tests/VObject/VCardConverterTest.php +++ b/tests/VObject/VCardConverterTest.php @@ -14,21 +14,21 @@ class VCardConverterTest extends TestCase */ public function testConvert30to40(): void { - $input = <<expectException(\InvalidArgumentException::class); - $input = <<expectException(\InvalidArgumentException::class); - $input = << + + - - - ../lib/ - - . + + + ../lib/ + + From 7de87c73e964e0ded3d409d797c1dc7bfedf664c Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Thu, 11 Jun 2026 19:37:09 +0930 Subject: [PATCH 2/9] test: fix unit test things --- tests/VObject/Component/VAlarmTest.php | 2 +- tests/VObject/Component/VCalendarTest.php | 2 +- tests/VObject/Component/VCardTest.php | 2 +- tests/VObject/Component/VEventTest.php | 2 +- tests/VObject/Component/VJournalTest.php | 2 +- tests/VObject/Component/VTodoTest.php | 2 +- tests/VObject/ComponentTest.php | 2 +- tests/VObject/DateTimeParserTest.php | 2 +- tests/VObject/Parser/MimeDirTest.php | 4 +- .../Property/ICalendar/CalAddressTest.php | 2 +- .../Property/VCard/DateAndOrTimeTest.php | 2 +- tests/VObject/Recur/RRuleIteratorTest.php | 114 +++++++++--------- tests/VObject/TimeZoneUtilTest.php | 6 +- .../FindFromTimezoneMapTest.php | 2 +- 14 files changed, 73 insertions(+), 73 deletions(-) diff --git a/tests/VObject/Component/VAlarmTest.php b/tests/VObject/Component/VAlarmTest.php index a67991ff4..33da9410c 100644 --- a/tests/VObject/Component/VAlarmTest.php +++ b/tests/VObject/Component/VAlarmTest.php @@ -16,7 +16,7 @@ public function testInTimeRange(VAlarm $valarm, \DateTime $start, \DateTime $end self::assertEquals($outcome, $valarm->isInTimeRange($start, $end)); } - public function timeRangeTestData(): array + public static function timeRangeTestData(): array { $tests = []; diff --git a/tests/VObject/Component/VCalendarTest.php b/tests/VObject/Component/VCalendarTest.php index 847e17d02..ea1fe61b4 100644 --- a/tests/VObject/Component/VCalendarTest.php +++ b/tests/VObject/Component/VCalendarTest.php @@ -31,7 +31,7 @@ public function testExpand(string $input, string $output, string $timeZone = 'UT self::assertVObjectEqualsVObject($output, $vcal->serialize()); } - public function expandData(): array + public static function expandData(): array { $tests = []; diff --git a/tests/VObject/Component/VCardTest.php b/tests/VObject/Component/VCardTest.php index ff4979ed4..1ae83b81e 100644 --- a/tests/VObject/Component/VCardTest.php +++ b/tests/VObject/Component/VCardTest.php @@ -31,7 +31,7 @@ public function testValidate(string $input, array $expectedWarnings, string $exp ); } - public function validateData(): array + public static function validateData(): array { $tests = []; diff --git a/tests/VObject/Component/VEventTest.php b/tests/VObject/Component/VEventTest.php index 5944ea574..9c79c245d 100644 --- a/tests/VObject/Component/VEventTest.php +++ b/tests/VObject/Component/VEventTest.php @@ -14,7 +14,7 @@ public function testInTimeRange(VEvent $vevent, \DateTime $start, \DateTime $end self::assertEquals($outcome, $vevent->isInTimeRange($start, $end)); } - public function timeRangeTestData(): array + public static function timeRangeTestData(): array { $tests = []; diff --git a/tests/VObject/Component/VJournalTest.php b/tests/VObject/Component/VJournalTest.php index 98f478896..d0419253d 100644 --- a/tests/VObject/Component/VJournalTest.php +++ b/tests/VObject/Component/VJournalTest.php @@ -68,7 +68,7 @@ public function testValidateBroken(): void ); } - public function timeRangeTestData(): array + public static function timeRangeTestData(): array { $calendar = new VCalendar(); diff --git a/tests/VObject/Component/VTodoTest.php b/tests/VObject/Component/VTodoTest.php index 899d93982..5bd28c6fe 100644 --- a/tests/VObject/Component/VTodoTest.php +++ b/tests/VObject/Component/VTodoTest.php @@ -15,7 +15,7 @@ public function testInTimeRange(VTodo $vtodo, \DateTime $start, \DateTime $end, self::assertEquals($outcome, $vtodo->isInTimeRange($start, $end)); } - public function timeRangeTestData(): array + public static function timeRangeTestData(): array { $tests = []; diff --git a/tests/VObject/ComponentTest.php b/tests/VObject/ComponentTest.php index c48480983..8e930a564 100644 --- a/tests/VObject/ComponentTest.php +++ b/tests/VObject/ComponentTest.php @@ -546,7 +546,7 @@ public function testValidateRepairShouldDeduplicatePropertiesWhenValuesAreEqual( self::assertCount(1, $component->GIR); } - public function ruleData(): array + public static function ruleData(): array { return [ [[], 2], diff --git a/tests/VObject/DateTimeParserTest.php b/tests/VObject/DateTimeParserTest.php index cdf0fe990..2ae7a6897 100644 --- a/tests/VObject/DateTimeParserTest.php +++ b/tests/VObject/DateTimeParserTest.php @@ -189,7 +189,7 @@ public function testBadVCardTime(): void DateTimeParser::parseVCardTime('23:12:166'); } - public function vcardDates(): array + public static function vcardDates(): array { return [ [ diff --git a/tests/VObject/Parser/MimeDirTest.php b/tests/VObject/Parser/MimeDirTest.php index 2fb436d0f..4d27843be 100644 --- a/tests/VObject/Parser/MimeDirTest.php +++ b/tests/VObject/Parser/MimeDirTest.php @@ -101,7 +101,7 @@ public function testDecodeUnsupportedInlineCharset(): void $mimeDir->parse($vcard); } - public function provideEmptyParserInput(): array + public static function provideEmptyParserInput(): array { return [ [null, 'No input provided to parse'], @@ -212,7 +212,7 @@ public function testBrokenMultilineContentDoesBreakImport($vcalendar): void $mimeDir->parse($vcalendar); } - public function provideBrokenVCalendar(): array + public static function provideBrokenVCalendar(): array { return [[<<getJsonValue()); } - public function dates(): array + public static function dates(): array { return [ [ diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index ea049a170..639949209 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -43,11 +43,11 @@ public function test2HourlyOnDstTransition(string $start, array $expected): void ); } - public function dst2HourlyTransitionProvider(): iterable + public static function dst2HourlyTransitionProvider(): iterable { yield 'On transition start' => [ - 'Start' => '2023-03-26 00:00:00', - 'Expected' => [ + 'start' => '2023-03-26 00:00:00', + 'expected' => [ '2023-03-26 00:00:00', '2023-03-26 03:00:00', '2023-03-26 04:00:00', @@ -56,8 +56,8 @@ public function dst2HourlyTransitionProvider(): iterable ], ]; yield 'During transition' => [ - 'Start' => '2023-03-26 00:15:00', - 'Expected' => [ + 'start' => '2023-03-26 00:15:00', + 'expected' => [ '2023-03-26 00:15:00', '2023-03-26 03:15:00', '2023-03-26 04:15:00', @@ -66,8 +66,8 @@ public function dst2HourlyTransitionProvider(): iterable ], ]; yield 'On transition end' => [ - 'Start' => '2023-03-26 01:00:00', - 'Expected' => [ + 'start' => '2023-03-26 01:00:00', + 'expected' => [ '2023-03-26 01:00:00', '2023-03-26 03:00:00', '2023-03-26 05:00:00', @@ -76,8 +76,8 @@ public function dst2HourlyTransitionProvider(): iterable ], ]; yield 'After transition end' => [ - 'Start' => '2023-03-26 01:15:00', - 'Expected' => [ + 'start' => '2023-03-26 01:15:00', + 'expected' => [ '2023-03-26 01:15:00', '2023-03-26 03:15:00', '2023-03-26 05:15:00', @@ -101,11 +101,11 @@ public function testHourlyOnDstTransition(string $start, array $expected): void ); } - public function dst6HourlyTransitionProvider(): iterable + public static function dst6HourlyTransitionProvider(): iterable { yield 'On transition start' => [ - 'Start' => '2023-03-25 20:00:00', - 'Expected' => [ + 'start' => '2023-03-25 20:00:00', + 'expected' => [ '2023-03-25 20:00:00', '2023-03-26 03:00:00', '2023-03-26 08:00:00', @@ -114,8 +114,8 @@ public function dst6HourlyTransitionProvider(): iterable ], ]; yield 'During transition' => [ - 'Start' => '2023-03-25 20:15:00', - 'Expected' => [ + 'start' => '2023-03-25 20:15:00', + 'expected' => [ '2023-03-25 20:15:00', '2023-03-26 03:15:00', '2023-03-26 08:15:00', @@ -124,8 +124,8 @@ public function dst6HourlyTransitionProvider(): iterable ], ]; yield 'On transition end' => [ - 'Start' => '2023-03-25 21:00:00', - 'Expected' => [ + 'start' => '2023-03-25 21:00:00', + 'expected' => [ '2023-03-25 21:00:00', '2023-03-26 03:00:00', '2023-03-26 09:00:00', @@ -134,8 +134,8 @@ public function dst6HourlyTransitionProvider(): iterable ], ]; yield 'After transition end' => [ - 'Start' => '2023-03-25 21:15:00', - 'Expected' => [ + 'start' => '2023-03-25 21:15:00', + 'expected' => [ '2023-03-25 21:15:00', '2023-03-26 03:15:00', '2023-03-26 09:15:00', @@ -292,11 +292,11 @@ public function testDailyOnDstTransition(string $start, array $expected): void ); } - public function dstDailyTransitionProvider(): iterable + public static function dstDailyTransitionProvider(): iterable { yield 'On transition start' => [ - 'Start' => '2023-03-24 02:00:00', - 'Expected' => [ + 'start' => '2023-03-24 02:00:00', + 'expected' => [ '2023-03-24 02:00:00', '2023-03-25 02:00:00', '2023-03-26 03:00:00', @@ -305,8 +305,8 @@ public function dstDailyTransitionProvider(): iterable ], ]; yield 'During transition' => [ - 'Start' => '2023-03-24 02:15:00', - 'Expected' => [ + 'start' => '2023-03-24 02:15:00', + 'expected' => [ '2023-03-24 02:15:00', '2023-03-25 02:15:00', '2023-03-26 03:15:00', @@ -315,8 +315,8 @@ public function dstDailyTransitionProvider(): iterable ], ]; yield 'On transition end' => [ - 'Start' => '2023-03-24 03:00:00', - 'Expected' => [ + 'start' => '2023-03-24 03:00:00', + 'expected' => [ '2023-03-24 03:00:00', '2023-03-25 03:00:00', '2023-03-26 03:00:00', @@ -325,8 +325,8 @@ public function dstDailyTransitionProvider(): iterable ], ]; yield 'After transition end' => [ - 'Start' => '2023-03-24 03:15:00', - 'Expected' => [ + 'start' => '2023-03-24 03:15:00', + 'expected' => [ '2023-03-24 03:15:00', '2023-03-25 03:15:00', '2023-03-26 03:15:00', @@ -499,11 +499,11 @@ public function testWeeklyOnDstTransition(string $start, array $expected): void ); } - public function dstWeeklyTransitionProvider(): iterable + public static function dstWeeklyTransitionProvider(): iterable { yield 'On transition start' => [ - 'Start' => '2023-03-12 02:00:00', - 'Expected' => [ + 'start' => '2023-03-12 02:00:00', + 'expected' => [ '2023-03-12 02:00:00', '2023-03-19 02:00:00', '2023-03-26 03:00:00', @@ -512,8 +512,8 @@ public function dstWeeklyTransitionProvider(): iterable ], ]; yield 'During transition' => [ - 'Start' => '2023-03-12 02:15:00', - 'Expected' => [ + 'start' => '2023-03-12 02:15:00', + 'expected' => [ '2023-03-12 02:15:00', '2023-03-19 02:15:00', '2023-03-26 03:15:00', @@ -522,8 +522,8 @@ public function dstWeeklyTransitionProvider(): iterable ], ]; yield 'On transition end' => [ - 'Start' => '2023-03-12 03:00:00', - 'Expected' => [ + 'start' => '2023-03-12 03:00:00', + 'expected' => [ '2023-03-12 03:00:00', '2023-03-19 03:00:00', '2023-03-26 03:00:00', @@ -532,8 +532,8 @@ public function dstWeeklyTransitionProvider(): iterable ], ]; yield 'After transition end' => [ - 'Start' => '2023-03-12 03:15:00', - 'Expected' => [ + 'start' => '2023-03-12 03:15:00', + 'expected' => [ '2023-03-12 03:15:00', '2023-03-19 03:15:00', '2023-03-26 03:15:00', @@ -751,11 +751,11 @@ public function testMonthlyOnDstTransition(string $start, array $expected): void ); } - public function dstMonthlyTransitionProvider(): iterable + public static function dstMonthlyTransitionProvider(): iterable { yield 'On transition start' => [ - 'Start' => '2023-01-26 02:00:00', - 'Expected' => [ + 'start' => '2023-01-26 02:00:00', + 'expected' => [ '2023-01-26 02:00:00', '2023-02-26 02:00:00', '2023-03-26 03:00:00', @@ -764,8 +764,8 @@ public function dstMonthlyTransitionProvider(): iterable ], ]; yield 'During transition' => [ - 'Start' => '2023-01-26 02:15:00', - 'Expected' => [ + 'start' => '2023-01-26 02:15:00', + 'expected' => [ '2023-01-26 02:15:00', '2023-02-26 02:15:00', '2023-03-26 03:15:00', @@ -774,8 +774,8 @@ public function dstMonthlyTransitionProvider(): iterable ], ]; yield 'On transition end' => [ - 'Start' => '2023-01-26 03:00:00', - 'Expected' => [ + 'start' => '2023-01-26 03:00:00', + 'expected' => [ '2023-01-26 03:00:00', '2023-02-26 03:00:00', '2023-03-26 03:00:00', @@ -784,8 +784,8 @@ public function dstMonthlyTransitionProvider(): iterable ], ]; yield 'After transition end' => [ - 'Start' => '2023-01-26 03:15:00', - 'Expected' => [ + 'start' => '2023-01-26 03:15:00', + 'expected' => [ '2023-01-26 03:15:00', '2023-02-26 03:15:00', '2023-03-26 03:15:00', @@ -794,8 +794,8 @@ public function dstMonthlyTransitionProvider(): iterable ], ]; yield 'During transition on 31st day of month' => [ - 'Start' => '2024-01-31 02:15:00', - 'Expected' => [ + 'start' => '2024-01-31 02:15:00', + 'expected' => [ '2024-01-31 02:15:00', '2024-03-31 03:15:00', '2024-05-31 02:15:00', @@ -1160,11 +1160,11 @@ public function testYearlyOnDstTransition(string $start, array $expected): void ); } - public function dstYearlyTransitionProvider(): iterable + public static function dstYearlyTransitionProvider(): iterable { yield 'On transition start' => [ - 'Start' => '2021-03-26 02:00:00', - 'Expected' => [ + 'start' => '2021-03-26 02:00:00', + 'expected' => [ '2021-03-26 02:00:00', '2022-03-26 02:00:00', '2023-03-26 03:00:00', @@ -1173,8 +1173,8 @@ public function dstYearlyTransitionProvider(): iterable ], ]; yield 'During transition' => [ - 'Start' => '2021-03-26 02:15:00', - 'Expected' => [ + 'start' => '2021-03-26 02:15:00', + 'expected' => [ '2021-03-26 02:15:00', '2022-03-26 02:15:00', '2023-03-26 03:15:00', @@ -1183,8 +1183,8 @@ public function dstYearlyTransitionProvider(): iterable ], ]; yield 'On transition end' => [ - 'Start' => '2021-03-26 03:00:00', - 'Expected' => [ + 'start' => '2021-03-26 03:00:00', + 'expected' => [ '2021-03-26 03:00:00', '2022-03-26 03:00:00', '2023-03-26 03:00:00', @@ -1193,8 +1193,8 @@ public function dstYearlyTransitionProvider(): iterable ], ]; yield 'After transition end' => [ - 'Start' => '2021-03-26 03:15:00', - 'Expected' => [ + 'start' => '2021-03-26 03:15:00', + 'expected' => [ '2021-03-26 03:15:00', '2022-03-26 03:15:00', '2023-03-26 03:15:00', @@ -1427,7 +1427,7 @@ public function testYearlyStartDateNotOnRRuleList(string $rule, string $start, a $this->parse($rule, $start, $expected); } - public function yearlyStartDateNotOnRRuleListProvider(): array + public static function yearlyStartDateNotOnRRuleListProvider(): array { return [ [ diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index bc45de8e5..01d5a7bf6 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -28,7 +28,7 @@ public function testCorrectTZ(string $timezoneName): void } } - public function getMapping(): array + public static function getMapping(): array { $map = array_merge( include __DIR__.'/../../lib/timezonedata/windowszones.php', @@ -196,7 +196,7 @@ public function testTimeZoneBCIdentifiers(string $tzid): void self::assertEquals($ex->getName(), $tz->getName()); } - public function getPHPTimeZoneIdentifiers(): array + public static function getPHPTimeZoneIdentifiers(): array { // PHPUNit requires an array of arrays return array_map( @@ -205,7 +205,7 @@ public function getPHPTimeZoneIdentifiers(): array ); } - public function getPHPTimeZoneBCIdentifiers(): array + public static function getPHPTimeZoneBCIdentifiers(): array { // PHPUNit requires an array of arrays return array_map( diff --git a/tests/VObject/TimezoneGuesser/FindFromTimezoneMapTest.php b/tests/VObject/TimezoneGuesser/FindFromTimezoneMapTest.php index abda23823..05ebd267c 100644 --- a/tests/VObject/TimezoneGuesser/FindFromTimezoneMapTest.php +++ b/tests/VObject/TimezoneGuesser/FindFromTimezoneMapTest.php @@ -23,7 +23,7 @@ public function testUpdatedTimezonesResolve(string $mapKey, string $expectedOlso self::assertSame($expectedOlson, $tz->getName()); } - public function updatedTimezoneProvider(): array + public static function updatedTimezoneProvider(): array { return [ // windowszones.php From 5a4792f01e958f06c7b9b443f784bbe1cc22ceab Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Sun, 14 Jun 2026 13:19:36 +0930 Subject: [PATCH 3/9] fix: throw InvalidDataException if Cli tool cannot open input file And adjust the related unit test testConvertDefaultFormats so that it handles the PHP warning that is emitted by fopen. The Cli code catches InvalidDataException and exits with status 2, which is what was always expected by the unit test code. So this should preserve the real behavior. The test changes are necessary because PHPunit11 now exposes the PHP warning coming from fopen. So we catch and expect that in an error handler in the test. --- lib/Cli.php | 3 +++ tests/VObject/CliTest.php | 52 +++++++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/lib/Cli.php b/lib/Cli.php index 38db47334..f5a150dea 100644 --- a/lib/Cli.php +++ b/lib/Cli.php @@ -594,6 +594,9 @@ protected function readInput(): ?Document if (!$this->parser) { if ('-' !== $this->inputPath) { $this->stdin = fopen($this->inputPath, 'r'); + if (false === $this->stdin) { + throw new InvalidDataException('Cannot open "'.$this->inputPath); + } } if ('mimedir' === $this->inputFormat) { diff --git a/tests/VObject/CliTest.php b/tests/VObject/CliTest.php index 6801593b2..2789ba050 100644 --- a/tests/VObject/CliTest.php +++ b/tests/VObject/CliTest.php @@ -272,30 +272,46 @@ public function testConvertMimeDir(): void ); } - public function testConvertDefaultFormats(): void + public static function provideFormats(): array { - $outputFile = $this->sabreTempDir.'bar.json'; - - self::assertEquals( - 2, - $this->cli->main(['vobject', 'convert', 'foo.json', $outputFile]) - ); - - self::assertEquals('json', $this->cli->inputFormat); - self::assertEquals('json', $this->cli->format); + return [ + ['foo.json', 'bar.json', 'json'], + ['foo.ics', 'bar.ics', 'mimedir'], + ]; } - public function testConvertDefaultFormats2(): void + /** + * @dataProvider provideFormats + */ + public function testConvertDefaultFormats($inputFilename, $outputFilename, $format): void { - $outputFile = $this->sabreTempDir.'bar.ics'; + $triggeredWarning = null; - self::assertEquals( - 2, - $this->cli->main(['vobject', 'convert', 'foo.ics', $outputFile]) - ); + // Set an error handler to catch the native PHP Warning + set_error_handler(function (int $errno, string $errstr) use (&$triggeredWarning) { + $triggeredWarning = $errstr; + + return true; // Prevents the error from propagating further + }, E_WARNING); + + $outputFile = $this->sabreTempDir.$outputFilename; - self::assertEquals('mimedir', $this->cli->inputFormat); - self::assertEquals('mimedir', $this->cli->format); + try { + self::assertEquals( + 2, + $this->cli->main(['vobject', 'convert', $inputFilename, $outputFile]) + ); + } finally { + restore_error_handler(); + } + + self::assertNotNull($triggeredWarning, 'A PHP warning was expected but never triggered.'); + $this->assertStringContainsString( + "fopen($inputFilename): Failed to open stream: No such file or directory", + $triggeredWarning + ); + self::assertEquals($format, $this->cli->inputFormat); + self::assertEquals($format, $this->cli->format); } public function testVCard3040(): void From 38acfdf41c905d7840b3f7f67d5455bd06c5397a Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Sun, 14 Jun 2026 13:45:11 +0930 Subject: [PATCH 4/9] test: fix PHPUnitAssertions to pass phpstan --- lib/PHPUnitAssertions.php | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/PHPUnitAssertions.php b/lib/PHPUnitAssertions.php index a201379be..ebf9956c6 100644 --- a/lib/PHPUnitAssertions.php +++ b/lib/PHPUnitAssertions.php @@ -41,7 +41,7 @@ public function assertVObjectEqualsVObject($expected, $actual, string $message = $input = Reader::read($input); } if (!$input instanceof Component) { - $this->fail('Input must be a string, stream or VObject component'); + $this::fail('Input must be a string, stream or VObject component'); } unset($input->PRODID); if ($input instanceof Component\VCalendar && 'GREGORIAN' === (string) $input->CALSCALE) { @@ -51,23 +51,26 @@ public function assertVObjectEqualsVObject($expected, $actual, string $message = return $input; }; - $expected = $getObj($expected)->serialize(); - $actual = $getObj($actual)->serialize(); + /** + * @var string $expectedSerialized + */ + $expectedSerialized = $getObj($expected)->serialize(); + $actualSerialized = $getObj($actual)->serialize(); // Finding wildcards in expected. - preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', (string) $expected, $matches, PREG_SET_ORDER); + preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', $expectedSerialized, $matches, PREG_SET_ORDER); foreach ($matches as $match) { - $actual = preg_replace( + $actualSerialized = preg_replace( '|^'.preg_quote($match[1], '|').':(.*)\r$|m', $match[1].':**ANY**'."\r", - (string) $actual + (string) $actualSerialized ); } - $this->assertEquals( - $expected, - $actual, + $this::assertEquals( + $expectedSerialized, + $actualSerialized, $message ); } From 67504c4e0ea1b3d8de07709b2a2265355a57b9f4 Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Sun, 14 Jun 2026 13:59:48 +0930 Subject: [PATCH 5/9] test: adjust test code to pass PHPstan --- tests/VObject/CliTest.php | 4 +- tests/VObject/Component/VAvailabilityTest.php | 2 +- tests/VObject/ITip/BrokerTester.php | 6 ++- tests/VObject/Parser/MimeDirTest.php | 1 + tests/VObject/Parser/XmlTest.php | 2 +- .../Property/ICalendar/DateTimeTest.php | 2 +- .../VObject/Recur/EventIterator/MainTest.php | 44 +++++++++---------- tests/VObject/Splitter/ICalendarTest.php | 4 +- tests/VObject/Splitter/VCardTest.php | 1 + tests/VObject/TimeZoneUtilTest.php | 6 +-- 10 files changed, 39 insertions(+), 33 deletions(-) diff --git a/tests/VObject/CliTest.php b/tests/VObject/CliTest.php index 2789ba050..4b3671778 100644 --- a/tests/VObject/CliTest.php +++ b/tests/VObject/CliTest.php @@ -154,7 +154,7 @@ public function testConvertJson(): void public function testConvertJCardPretty(): void { if (version_compare(PHP_VERSION, '5.4.0') < 0) { - $this->markTestSkipped('This test required PHP 5.4.0'); + $this::markTestSkipped('This test required PHP 5.4.0'); } $inputStream = fopen('php://memory', 'r+'); @@ -306,7 +306,7 @@ public function testConvertDefaultFormats($inputFilename, $outputFilename, $form } self::assertNotNull($triggeredWarning, 'A PHP warning was expected but never triggered.'); - $this->assertStringContainsString( + $this::assertStringContainsString( "fopen($inputFilename): Failed to open stream: No such file or directory", $triggeredWarning ); diff --git a/tests/VObject/Component/VAvailabilityTest.php b/tests/VObject/Component/VAvailabilityTest.php index c2ad50da8..e47653910 100644 --- a/tests/VObject/Component/VAvailabilityTest.php +++ b/tests/VObject/Component/VAvailabilityTest.php @@ -412,7 +412,7 @@ protected function assertIsValid(VObject\Document $document): void $validationResult = $document->validate(); if ($validationResult) { $messages = array_map(fn ($item) => $item['message'], $validationResult); - $this->fail('Failed to assert that the supplied document is a valid document. Validation messages: '.implode(', ', $messages)); + $this::fail('Failed to assert that the supplied document is a valid document. Validation messages: '.implode(', ', $messages)); } self::assertEmpty($document->validate()); } diff --git a/tests/VObject/ITip/BrokerTester.php b/tests/VObject/ITip/BrokerTester.php index c46ef4bb4..43eb91814 100644 --- a/tests/VObject/ITip/BrokerTester.php +++ b/tests/VObject/ITip/BrokerTester.php @@ -40,6 +40,7 @@ public function parse($oldMessage, $newMessage, array $expected = [], string $cu $message->message->serialize() ); } else { + // @phpstan-ignore property.dynamicName self::assertEquals($val, $message->$key); } } @@ -59,8 +60,9 @@ public function process($input, $existingObject = null, $expected = false): void $vcal = Reader::read($input); $mainComponent = new VEvent($vcal, 'VEVENT'); - foreach ($vcal->getComponents() as $mainComponent) { - if ('VEVENT' === $mainComponent->name) { + foreach ($vcal->getComponents() as $nextComponent) { + if ('VEVENT' === $nextComponent->name) { + $mainComponent = $nextComponent; break; } } diff --git a/tests/VObject/Parser/MimeDirTest.php b/tests/VObject/Parser/MimeDirTest.php index 4d27843be..a265b4216 100644 --- a/tests/VObject/Parser/MimeDirTest.php +++ b/tests/VObject/Parser/MimeDirTest.php @@ -274,6 +274,7 @@ public function testPropertyName0(): void $mimeDir = new MimeDir(); $vevent = $mimeDir->parse($iCal); + // @phpstan-ignore property.dynamicName self::assertEquals('test', $vevent->VEVENT->{0}->getValue()); } diff --git a/tests/VObject/Parser/XmlTest.php b/tests/VObject/Parser/XmlTest.php index 6491d087b..1ad36474d 100644 --- a/tests/VObject/Parser/XmlTest.php +++ b/tests/VObject/Parser/XmlTest.php @@ -2704,6 +2704,6 @@ protected function assertXMLReflexivelyEqualsToMimeDir(string $xml, string $mime self::assertXMLEqualsToMimeDir($xml, $mimedir); $component = VObject\Reader::read($mimedir); - self::assertXmlStringEqualsXmlString($xml, VObject\Writer::writeXML($component)); + self::assertXmlStringEqualsXmlString($xml, VObject\Writer::writeXml($component)); } } diff --git a/tests/VObject/Property/ICalendar/DateTimeTest.php b/tests/VObject/Property/ICalendar/DateTimeTest.php index 5a70b2641..f0728d3ed 100644 --- a/tests/VObject/Property/ICalendar/DateTimeTest.php +++ b/tests/VObject/Property/ICalendar/DateTimeTest.php @@ -102,7 +102,7 @@ public function testSetDateTimeDATE(): void self::assertEquals('19850704', (string) $elem); self::assertNull($elem['TZID']); - self::assertEquals('DATE', (string) $elem['VALUE']); + self::assertEquals('DATE', $elem['VALUE']); self::assertFalse($elem->hasTime()); } diff --git a/tests/VObject/Recur/EventIterator/MainTest.php b/tests/VObject/Recur/EventIterator/MainTest.php index bce4bcd92..1899feea3 100644 --- a/tests/VObject/Recur/EventIterator/MainTest.php +++ b/tests/VObject/Recur/EventIterator/MainTest.php @@ -33,7 +33,7 @@ public function testValues(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); self::assertTrue($it->isInfinite()); } @@ -58,7 +58,7 @@ public function testInvalidFreq(): void $ev->add($dtStart); $vcal->add($ev); - new EventIterator($vcal, (string) $ev->UID); + new EventIterator($vcal, $ev->UID); } /** @@ -252,7 +252,7 @@ public function testDailyByDayByHour(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); // Grabbing the next 12 items $max = 12; @@ -304,7 +304,7 @@ public function testDailyByHour(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); // Grabbing the next 12 items $max = 12; @@ -356,7 +356,7 @@ public function testDailyByDay(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); // Grabbing the next 12 items $max = 12; @@ -408,7 +408,7 @@ public function testWeekly(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); // Max is to prevent overflow $max = 12; @@ -458,7 +458,7 @@ public function testWeeklyByDayByHour(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); // Grabbing the next 12 items $max = 15; @@ -513,7 +513,7 @@ public function testWeeklyByDaySpecificHour(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); // Grabbing the next 12 items $max = 12; @@ -565,7 +565,7 @@ public function testWeeklyByDay(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); // Grabbing the next 12 items $max = 12; @@ -617,7 +617,7 @@ public function testMonthly(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); $max = 14; $result = []; @@ -661,7 +661,7 @@ public function testMonthlyEndOfMonth(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); $max = 14; $result = []; @@ -719,7 +719,7 @@ public function testMonthlyByMonthDay(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); $max = 14; $result = []; @@ -772,7 +772,7 @@ public function testMonthlyByDay(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); $max = 20; $result = []; @@ -827,7 +827,7 @@ public function testMonthlyByDayByMonthDay(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); $max = 20; $result = []; @@ -876,7 +876,7 @@ public function testMonthlyByDayBySetPos(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); $max = 20; $result = []; @@ -930,7 +930,7 @@ public function testYearly(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); $max = 20; $result = []; @@ -979,7 +979,7 @@ public function testYearlyLeapYear(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); $max = 20; $result = []; @@ -1021,7 +1021,7 @@ public function testYearlyByMonth(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); $max = 20; $result = []; @@ -1068,7 +1068,7 @@ public function testYearlyByMonthByDay(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); $max = 20; $result = []; @@ -1115,7 +1115,7 @@ public function testFastForward(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); // The idea is that we're fast-forwarding too far in the future, so // there will be no results left. @@ -1157,7 +1157,7 @@ public function testFastForwardAllDayEventThatStopAtTheStartTime(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); $it->fastForward(new \DateTimeImmutable('2011-04-05T000000', new \DateTimeZone('UTC'))); @@ -1199,7 +1199,7 @@ public function testComplexExclusions(): void $vcal->add($ev); - $it = new EventIterator($vcal, (string) $ev->UID); + $it = new EventIterator($vcal, $ev->UID); $max = 20; $result = []; diff --git a/tests/VObject/Splitter/ICalendarTest.php b/tests/VObject/Splitter/ICalendarTest.php index 0688e378b..775496dc5 100644 --- a/tests/VObject/Splitter/ICalendarTest.php +++ b/tests/VObject/Splitter/ICalendarTest.php @@ -94,6 +94,7 @@ public function testICalendarImportInvalidEvent(): void public function testICalendarImportMultipleValidEvents(): void { + $event = []; $event[] = << $item['message'], $messages ); - $this->fail('Validation errors: '.implode("\n", $messages)); + $this::fail('Validation errors: '.implode("\n", $messages)); } else { self::assertEquals([], $messages); } @@ -217,6 +218,7 @@ public function testICalendarImportMultipleVTIMEZONESAndMultipleValidEvents(): v END:VTIMEZONE EOT; + $event = []; $event[] = <<expectException(ParseException::class); + $event = []; $event[] = <<getMessage(), 'Unknown or bad timezone')) { - $this->markTestSkipped($timezoneName.' is not (yet) supported in this PHP version. Update pecl/timezonedb'); + $this::markTestSkipped($timezoneName.' is not (yet) supported in this PHP version. Update pecl/timezonedb'); } else { throw $e; } @@ -187,8 +187,8 @@ public function testTimeZoneBCIdentifiers(string $tzid): void * that should be released in Feb 2023. */ $versionOfPHP = \phpversion(); - if ((('8.1.14' == $versionOfPHP) || ('8.2.1' == $versionOfPHP)) && \str_contains($tzid, '+')) { - $this->markTestSkipped("Timezone ids containing '+' do not work on PHP $versionOfPHP"); + if ((('8.1.14' === $versionOfPHP) || ('8.2.1' === $versionOfPHP)) && \str_contains($tzid, '+')) { + $this::markTestSkipped("Timezone ids containing '+' do not work on PHP $versionOfPHP"); } $tz = TimeZoneUtil::getTimeZone($tzid); $ex = new \DateTimeZone($tzid); From 2eca60cd99c0f2bf77055e096bfa64c061bef52e Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Sun, 14 Jun 2026 16:52:47 +0930 Subject: [PATCH 6/9] fix: adjust code so that phpstan passes --- lib/BirthdayCalendarGenerator.php | 2 +- lib/Cli.php | 3 +- lib/Component.php | 4 +- lib/Component/VAlarm.php | 2 +- lib/Component/VCalendar.php | 2 +- lib/Component/VCard.php | 6 +- lib/DateTimeParser.php | 6 +- lib/ITip/Broker.php | 30 ++++++---- lib/Parameter.php | 3 +- lib/Parser/MimeDir.php | 6 +- lib/Parser/XML.php | 6 +- lib/Property.php | 6 +- lib/Property/Binary.php | 2 +- lib/Property/ICalendar/DateTime.php | 4 +- lib/Property/ICalendar/Recur.php | 20 +++---- lib/Property/Text.php | 6 +- lib/Property/Uri.php | 2 +- lib/Property/VCard/DateAndOrTime.php | 2 +- lib/Recur/RRuleIterator.php | 59 ++++++++++--------- .../FindFromTimezoneIdentifier.php | 4 +- lib/VCardConverter.php | 19 +++--- 21 files changed, 103 insertions(+), 91 deletions(-) diff --git a/lib/BirthdayCalendarGenerator.php b/lib/BirthdayCalendarGenerator.php index 6fb21f4f2..11ae8be14 100644 --- a/lib/BirthdayCalendarGenerator.php +++ b/lib/BirthdayCalendarGenerator.php @@ -95,7 +95,7 @@ public function getResult(): VCalendar // We've seen clients (ez-vcard) putting "BDAY:" properties // without a value into vCards. If we come across those, we'll // skip them. - if (empty($object->BDAY->getValue())) { + if ('' === $object->BDAY->getValue()) { continue; } diff --git a/lib/Cli.php b/lib/Cli.php index f5a150dea..386cf2c73 100644 --- a/lib/Cli.php +++ b/lib/Cli.php @@ -147,7 +147,7 @@ public function main(array $argv): int throw new \InvalidArgumentException('Too many arguments'); } - if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'])) { + if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'], true)) { throw new \InvalidArgumentException('Unknown command: '.$positional[0]); } } catch (\InvalidArgumentException $e) { @@ -185,6 +185,7 @@ public function main(array $argv): int try { while ($input = $this->readInput()) { + // @phpstan-ignore method.dynamicName $returnCode = $this->$command($input); if (0 !== $returnCode) { $realCode = $returnCode; diff --git a/lib/Component.php b/lib/Component.php index b1a05d9aa..0849c5a8e 100644 --- a/lib/Component.php +++ b/lib/Component.php @@ -366,7 +366,7 @@ public function xmlSerialize(Xml\Writer $writer): void $writer->startElement(strtolower($this->name)); - if (!empty($properties)) { + if ([] !== $properties) { $writer->startElement('properties'); foreach ($properties as $property) { @@ -376,7 +376,7 @@ public function xmlSerialize(Xml\Writer $writer): void $writer->endElement(); } - if (!empty($components)) { + if ([] !== $components) { $writer->startElement('components'); foreach ($components as $component) { diff --git a/lib/Component/VAlarm.php b/lib/Component/VAlarm.php index 0e7f05ce0..47474dfbf 100644 --- a/lib/Component/VAlarm.php +++ b/lib/Component/VAlarm.php @@ -34,7 +34,7 @@ public function getEffectiveTriggerTime(): \DateTimeImmutable $trigger = $this->TRIGGER; if (!isset($trigger['VALUE']) || ($trigger['VALUE'] && 'DURATION' === strtoupper((string) $trigger['VALUE']))) { $triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER); - $related = (isset($trigger['RELATED']) && 'END' == strtoupper($trigger['RELATED'])) ? 'END' : 'START'; + $related = (isset($trigger['RELATED']) && 'END' === strtoupper($trigger['RELATED'])) ? 'END' : 'START'; /** @var VEvent|VTodo $parentComponent */ $parentComponent = $this->parent; diff --git a/lib/Component/VCalendar.php b/lib/Component/VCalendar.php index a4dff82c9..46928a62d 100644 --- a/lib/Component/VCalendar.php +++ b/lib/Component/VCalendar.php @@ -424,7 +424,7 @@ public function validate(int $options = 0): array if ($child instanceof Component) { ++$componentsFound; - if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'])) { + if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'], true)) { continue; } $componentTypes[] = $child->name; diff --git a/lib/Component/VCard.php b/lib/Component/VCard.php index 1fe654712..7c5ca739c 100644 --- a/lib/Component/VCard.php +++ b/lib/Component/VCard.php @@ -491,7 +491,7 @@ public function xmlSerialize(Xml\Writer $writer): void $writer->startElement(strtolower($this->name)); foreach ($propertiesByGroup as $group => $properties) { - if (!empty($group)) { + if ('' !== $group) { $writer->startElement('group'); $writer->writeAttribute('name', strtolower((string) $group)); } @@ -513,7 +513,7 @@ public function xmlSerialize(Xml\Writer $writer): void } } - if (!empty($group)) { + if ('' !== $group) { $writer->endElement(); } } @@ -529,7 +529,7 @@ public function getClassNameForPropertyName(string $propertyName): string $className = parent::getClassNameForPropertyName($propertyName); // In vCard 4, BINARY no longer exists, and we need URI instead. - if (VObject\Property\Binary::class == $className && self::VCARD40 === $this->getDocumentType()) { + if (VObject\Property\Binary::class === $className && self::VCARD40 === $this->getDocumentType()) { return VObject\Property\Uri::class; } diff --git a/lib/DateTimeParser.php b/lib/DateTimeParser.php index 44e27c6fa..9469cacea 100644 --- a/lib/DateTimeParser.php +++ b/lib/DateTimeParser.php @@ -329,7 +329,7 @@ public static function parseVCardDateTime(string $date): array $result = []; foreach ($parts as $part) { - if (empty($matches[$part])) { + if (!array_key_exists($part, $matches) || '' === $matches[$part]) { $result[$part] = null; } elseif ('-' === $matches[$part] || '--' === $matches[$part]) { $result[$part] = null; @@ -424,7 +424,7 @@ public static function parseVCardTime(string $date): array $result = []; foreach ($parts as $part) { - if (empty($matches[$part])) { + if (!array_key_exists($part, $matches) || '' === $matches[$part]) { $result[$part] = null; } elseif ('-' === $matches[$part]) { $result[$part] = null; @@ -541,7 +541,7 @@ public static function parseVCardDateAndOrTime(string $date): array $parts['year0'] = &$parts['year']; foreach ($parts as $part => &$value) { - if (!empty($matches[$part])) { + if (array_key_exists($part, $matches) && '' !== $matches[$part]) { $value = $matches[$part]; } } diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index f36b32787..42862cd8b 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -228,7 +228,7 @@ public function parseEvent($calendar, $userHref, $oldCalendar = null): array $eventInfo = $oldEventInfo; - if (in_array($eventInfo['organizer'], $userHref)) { + if (in_array($eventInfo['organizer'], $userHref, true)) { // This is an organizer deleting the event. $eventInfo['attendees'] = []; // Increasing the sequence, but only if the organizer deleted @@ -237,7 +237,7 @@ public function parseEvent($calendar, $userHref, $oldCalendar = null): array } else { // This is an attendee deleting the event. foreach ($eventInfo['attendees'] as $key => $attendee) { - if (in_array($attendee['href'], $userHref)) { + if (in_array($attendee['href'], $userHref, true)) { $eventInfo['attendees'][$key]['instances'] = ['master' => ['id' => 'master', 'partstat' => 'DECLINED'], ]; } @@ -247,13 +247,13 @@ public function parseEvent($calendar, $userHref, $oldCalendar = null): array } // Check if the user is the organizer - if (in_array($eventInfo['organizer'], $userHref)) { + if (in_array($eventInfo['organizer'], $userHref, true)) { return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo); } // Check if the user is an attendee foreach ($eventInfo['attendees'] as $attendee) { - if (in_array($attendee['href'], $userHref)) { + if (in_array($attendee['href'], $userHref, true)) { // If this is a event update, we always generate a reply if ($oldCalendar) { return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']); @@ -566,7 +566,7 @@ protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, $message->significantChange = 'REQUEST' === $attendee['forceSend'] - || count($oldAttendeeInstances) != count($newAttendeeInstances) + || count($oldAttendeeInstances) !== count($newAttendeeInstances) || count(array_diff($oldAttendeeInstances, $newAttendeeInstances)) > 0 || $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash']; @@ -576,9 +576,9 @@ protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, // We need to find a list of events that the attendee // is not a part of to add to the list of exceptions. $exceptions = []; - foreach ($eventInfo['instances'] as $instanceId => $vevent) { - if (!isset($attendee['newInstances'][$instanceId])) { - $exceptions[] = $instanceId; + foreach ($eventInfo['instances'] as $eventInstanceId => $vevent) { + if (!isset($attendee['newInstances'][$eventInstanceId])) { + $exceptions[] = $eventInstanceId; } } @@ -648,7 +648,12 @@ protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, return []; } - $oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ? + $oldInstances = + is_array($oldEventInfo['attendees']) + && array_key_exists($attendee, $oldEventInfo['attendees']) + && is_array($oldEventInfo['attendees'][$attendee]) + && array_key_exists('instances', $oldEventInfo['attendees'][$attendee]) + && is_array($oldEventInfo['attendees'][$attendee]['instances']) ? $oldEventInfo['attendees'][$attendee]['instances'] : []; @@ -679,7 +684,7 @@ protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, // We only need to do that though, if the master event is not declined. if (isset($instances['master']) && 'DECLINED' !== $instances['master']['newstatus']) { foreach ($eventInfo['exdate'] as $exDate) { - if (!in_array($exDate, $oldEventInfo['exdate'] ?? [])) { + if (!in_array($exDate, $oldEventInfo['exdate'] ?? [], true)) { if (isset($instances[$exDate])) { $instances[$exDate]['newstatus'] = 'DECLINED'; } else { @@ -722,7 +727,7 @@ protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, $hasReply = false; foreach ($instances as $instance) { - if ($instance['oldstatus'] == $instance['newstatus'] && 'REPLY' !== $eventInfo['organizerForceSend']) { + if ($instance['oldstatus'] === $instance['newstatus'] && 'REPLY' !== $eventInfo['organizerForceSend']) { // Skip continue; } @@ -884,7 +889,7 @@ protected function parseEventInfo(VCalendar $calendar): array foreach ($vevent->select('RRULE') as $rr) { foreach ($rr->getParts() as $key => $val) { // ignore default values (https://github.com/sabre-io/vobject/issues/126) - if ('INTERVAL' === $key && 1 == $val) { + if ('INTERVAL' === $key && '1' === (string) $val) { continue; } if (is_array($val)) { @@ -951,6 +956,7 @@ protected function parseEventInfo(VCalendar $calendar): array } foreach ($this->significantChangeProperties as $prop) { + // @phpstan-ignore property.dynamicName if (isset($vevent->$prop)) { $propertyValues = $vevent->select($prop); diff --git a/lib/Parameter.php b/lib/Parameter.php index 5b0804ec5..64f77c187 100644 --- a/lib/Parameter.php +++ b/lib/Parameter.php @@ -163,7 +163,8 @@ public function has(string $value): bool { return in_array( strtolower($value), - array_map(strtolower(...), (array) $this->value) + array_map(strtolower(...), (array) $this->value), + true ); } diff --git a/lib/Parser/MimeDir.php b/lib/Parser/MimeDir.php index b71ae2634..47fa972ce 100644 --- a/lib/Parser/MimeDir.php +++ b/lib/Parser/MimeDir.php @@ -111,7 +111,7 @@ public function parse($input = null, int $options = 0): ?Document */ public function setCharset(string $charset): void { - if (!in_array($charset, self::$SUPPORTED_CHARSETS)) { + if (!in_array($charset, self::$SUPPORTED_CHARSETS, true)) { throw new \InvalidArgumentException('Unsupported encoding. (Supported encodings: '.implode(', ', self::$SUPPORTED_CHARSETS).')'); } $this->charset = $charset; @@ -396,7 +396,7 @@ protected function readProperty(string $line) throw new ParseException('Invalid Mimedir file. Line starting at '.$this->startLine.' did not follow iCalendar/vCard conventions'); } - if ('=' == $match[0][0] && self::TOKEN_PARAMNAME != $lastToken) { + if ('=' === $match[0][0] && self::TOKEN_PARAMNAME !== $lastToken) { throw new ParseException('Invalid Mimedir file. Line starting at '.$this->startLine.': Missing parameter name for parameter value "'.$match['paramValue'].'"'); } @@ -443,7 +443,7 @@ protected function readProperty(string $line) if (\is_null($property['value'])) { $property['value'] = ''; } - if (!isset($property['name']) || 0 == strlen($property['name'])) { + if (!isset($property['name']) || 0 === strlen($property['name'])) { if ($this->options & self::OPTION_IGNORE_INVALID_LINES) { return false; } diff --git a/lib/Parser/XML.php b/lib/Parser/XML.php index 77a229733..6fb3e5624 100644 --- a/lib/Parser/XML.php +++ b/lib/Parser/XML.php @@ -112,7 +112,7 @@ public function parse($input = null, int $options = 0): ?Document */ protected function parseVCalendarComponents(Component $parentComponent): void { - foreach ($this->pointer['value'] ?: [] as $children) { + foreach ($this->pointer['value'] ?? [] as $children) { switch (static::getTagName($children['name'])) { case 'properties': $this->pointer = &$children['value']; @@ -145,7 +145,7 @@ protected function parseVCardComponents(Component $parentComponent): void */ protected function parseProperties(Component $parentComponent, string $propertyNamePrefix = ''): void { - foreach ($this->pointer ?: [] as $xmlProperty) { + foreach ($this->pointer ?? [] as $xmlProperty) { [$namespace, $tagName] = SabreXml\Service::parseClarkNotation($xmlProperty['name']); $propertyName = $tagName; @@ -301,7 +301,7 @@ protected function parseProperties(Component $parentComponent, string $propertyN */ protected function parseComponent(Component $parentComponent): void { - $components = $this->pointer['value'] ?: []; + $components = $this->pointer['value'] ?? []; foreach ($components as $component) { $componentName = static::getTagName($component['name']); diff --git a/lib/Property.php b/lib/Property.php index e369b5239..7432e5064 100644 --- a/lib/Property.php +++ b/lib/Property.php @@ -104,7 +104,7 @@ public function setValue($value): void public function getValue() { if (is_array($this->value)) { - if (0 == count($this->value)) { + if (0 === count($this->value)) { return null; } elseif (1 === count($this->value)) { return $this->value[0]; @@ -312,7 +312,7 @@ public function xmlSerialize(Xml\Writer $writer): void $writer->startElement(strtolower((string) $this->name)); - if (!empty($parameters)) { + if ([] !== $parameters) { $writer->startElement('parameters'); foreach ($parameters as $parameter) { @@ -553,7 +553,7 @@ public function validate(int $options = 0): array } break; } - if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding)) { + if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding, true)) { $warnings[] = [ 'level' => 3, 'message' => 'ENCODING='.strtoupper($encoding).' is not valid for this document type.', diff --git a/lib/Property/Binary.php b/lib/Property/Binary.php index 124794c9d..9d68f415e 100644 --- a/lib/Property/Binary.php +++ b/lib/Property/Binary.php @@ -54,7 +54,7 @@ public function setValue($value): void */ public function setRawMimeDirValue(string $val): void { - $this->value = base64_decode($val); + $this->value = base64_decode($val, true); } /** diff --git a/lib/Property/ICalendar/DateTime.php b/lib/Property/ICalendar/DateTime.php index e4c876563..cd92475f3 100644 --- a/lib/Property/ICalendar/DateTime.php +++ b/lib/Property/ICalendar/DateTime.php @@ -205,7 +205,7 @@ public function setDateTimes(array $dt, $isFloating = false): void } if (is_null($tz)) { $tz = $d->getTimeZone(); - $isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z', '+00:00']); + $isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z', '+00:00'], true); if (!$isUtc) { $this->offsetSet('TZID', $tz->getName()); } @@ -257,7 +257,7 @@ public function getJsonValue(): array $isFloating = $this->isFloating(); $tz = $dts[0]->getTimeZone(); - $isUtc = !$isFloating && in_array($tz->getName(), ['UTC', 'GMT', 'Z']); + $isUtc = !$isFloating && in_array($tz->getName(), ['UTC', 'GMT', 'Z'], true); return array_map( function (\DateTimeInterface $dt) use ($hasTime, $isUtc) { diff --git a/lib/Property/ICalendar/Recur.php b/lib/Property/ICalendar/Recur.php index 66bb54970..f3d45158a 100644 --- a/lib/Property/ICalendar/Recur.php +++ b/lib/Property/ICalendar/Recur.php @@ -36,7 +36,7 @@ class Recur extends Property * * This may be either a single, or multiple strings in an array. * - * @param string|array $value + * @param string|array|object $value */ public function setValue($value): void { @@ -186,7 +186,7 @@ public static function stringToArray(string $value): array $newValue = []; foreach (explode(';', $value) as $part) { // Skipping empty parts. - if (empty($part)) { + if ('' === $part) { continue; } @@ -243,7 +243,7 @@ public function validate(int $options = 0): array if ($repair) { unset($values[$key]); } - } elseif ('BYMONTH' == $key) { + } elseif ('BYMONTH' === $key) { $byMonth = (array) $value; foreach ($byMonth as $i => $v) { if (!is_numeric($v) || (int) $v < 1 || (int) $v > 12) { @@ -262,13 +262,13 @@ public function validate(int $options = 0): array } } // if there is no valid entry left, remove the whole value - if (is_array($value) && empty($values[$key])) { + if (is_array($value) && ([] === $values[$key])) { unset($values[$key]); } - } elseif ('BYWEEKNO' == $key) { + } elseif ('BYWEEKNO' === $key) { $byWeekNo = (array) $value; foreach ($byWeekNo as $i => $v) { - if (!is_numeric($v) || (int) $v < -53 || 0 == (int) $v || (int) $v > 53) { + if (!is_numeric($v) || (int) $v < -53 || 0 === (int) $v || (int) $v > 53) { $warnings[] = [ 'level' => $repair ? 1 : 3, 'message' => 'BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!', @@ -284,13 +284,13 @@ public function validate(int $options = 0): array } } // if there is no valid entry left, remove the whole value - if (is_array($value) && empty($values[$key])) { + if (is_array($value) && ([] === $values[$key])) { unset($values[$key]); } - } elseif ('BYYEARDAY' == $key) { + } elseif ('BYYEARDAY' === $key) { $byYearDay = (array) $value; foreach ($byYearDay as $i => $v) { - if (!is_numeric($v) || (int) $v < -366 || 0 == (int) $v || (int) $v > 366) { + if (!is_numeric($v) || (int) $v < -366 || 0 === (int) $v || (int) $v > 366) { $warnings[] = [ 'level' => $repair ? 1 : 3, 'message' => 'BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!', @@ -306,7 +306,7 @@ public function validate(int $options = 0): array } } // if there is no valid entry left, remove the whole value - if (is_array($value) && empty($values[$key])) { + if (is_array($value) && ([] === $values[$key])) { unset($values[$key]); } } diff --git a/lib/Property/Text.php b/lib/Property/Text.php index 7b044346c..e585026fc 100644 --- a/lib/Property/Text.php +++ b/lib/Property/Text.php @@ -71,7 +71,7 @@ public function __construct(Component $root, string $name, $value = null, array // 2. structured value properties // // The former is always separated by a comma, the latter by semicolon. - if (in_array($name, $this->structuredValues)) { + if (in_array($name, $this->structuredValues, true)) { $this->delimiter = ';'; } @@ -155,7 +155,7 @@ public function getJsonValue(): array // Structured text values should always be returned as a single // array-item. Multi-value text should be returned as multiple items in // the top-array. - if (in_array($this->name, $this->structuredValues)) { + if (in_array($this->name, $this->structuredValues, true)) { return [$this->getParts()]; } @@ -270,7 +270,7 @@ protected function xmlSerializeValue(Xml\Writer $writer): void foreach ($items as $i => $item) { $writer->writeElement( $item, - !empty($values[$i]) ? $values[$i] : null + ('' !== $values[$i] && [] !== $values[$i]) ? $values[$i] : null ); } }; diff --git a/lib/Property/Uri.php b/lib/Property/Uri.php index ec98f7416..dce7bf4bb 100644 --- a/lib/Property/Uri.php +++ b/lib/Property/Uri.php @@ -38,7 +38,7 @@ public function getValueType(): string public function parameters(): array { $parameters = parent::parameters(); - if (!isset($parameters['VALUE']) && in_array($this->name, ['URL', 'PHOTO'])) { + if (!isset($parameters['VALUE']) && in_array($this->name, ['URL', 'PHOTO'], true)) { // If we are encoding a URI value, and this URI value has no // VALUE=URI parameter, we add it anyway. // diff --git a/lib/Property/VCard/DateAndOrTime.php b/lib/Property/VCard/DateAndOrTime.php index 7aa88a628..9a7693961 100644 --- a/lib/Property/VCard/DateAndOrTime.php +++ b/lib/Property/VCard/DateAndOrTime.php @@ -77,7 +77,7 @@ public function setValue($value): void public function setDateTime(\DateTimeInterface $dt): void { $tz = $dt->getTimeZone(); - $isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z']); + $isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z'], true); if ($isUtc) { $value = $dt->format('Ymd\\THis\\Z'); diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index b260b2d04..de1c123b5 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -354,23 +354,23 @@ protected function nextDaily(): void } $recurrenceHours = []; - if (!empty($this->byHour)) { + if ($this->byHour) { $recurrenceHours = $this->getHours(); } $recurrenceDays = []; - if (!empty($this->byDay)) { + if ($this->byDay) { $recurrenceDays = $this->getDays(); } $recurrenceMonths = []; - if (!empty($this->byMonth)) { + if ($this->byMonth) { $recurrenceMonths = $this->getMonths(); } do { if ($this->byHour) { - if ('23' == $this->currentDate->format('G')) { + if ('23' === $this->currentDate->format('G')) { // to obey the interval rule $this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' days'); } @@ -395,9 +395,9 @@ protected function nextDaily(): void return; } } while ( - ($this->byDay && !in_array($currentDay, $recurrenceDays)) - || ($this->byHour && !in_array($currentHour, $recurrenceHours)) - || ($this->byMonth && !in_array($currentMonth, $recurrenceMonths)) + ($this->byDay && !in_array((int) $currentDay, $recurrenceDays, true)) + || ($this->byHour && !in_array($currentHour, $recurrenceHours, true)) + || ($this->byMonth && !in_array($currentMonth, $recurrenceMonths, true)) ); } @@ -439,18 +439,18 @@ protected function nextWeekly(): void $currentHour = (int) $this->currentDate->format('G'); // We need to roll over to the next week - if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) { + if ($currentDay === $firstDay && (!$this->byHour || 0 === $currentHour)) { $this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' weeks'); // We need to go to the first day of this week, but only if we // are not already on this first day of this week. - if ($this->currentDate->format('w') != $firstDay) { + if ((int) $this->currentDate->format('w') !== $firstDay) { $this->currentDate = $this->currentDate->modify('last '.$this->dayNames[$this->dayMap[$this->weekStart]]); } } // We have a match - } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours))); + } while (($this->byDay && !in_array($currentDay, $recurrenceDays, true)) || ($this->byHour && !in_array((string) $currentHour, $recurrenceHours, true))); } /** @@ -473,14 +473,14 @@ protected function nextMonthly(): void ++$increase; $tempDate = clone $this->currentDate; $tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months '.$this->startTime()); - } while ($tempDate->format('j') != $currentDayOfMonth); + } while ($tempDate->format('j') !== $currentDayOfMonth); $this->currentDate = $tempDate; } return; } - $occurrence = -1; + $selectedOccurrence = -1; while (true) { $occurrences = $this->getMonthlyOccurrences(); @@ -488,6 +488,7 @@ protected function nextMonthly(): void // The first occurrence that's higher than the current // day of the month wins. if ($occurrence > $currentDayOfMonth) { + $selectedOccurrence = $occurrence; break 2; } } @@ -530,7 +531,7 @@ protected function nextMonthly(): void $this->currentDate = $this->currentDate->setDate( (int) $this->currentDate->format('Y'), (int) $this->currentDate->format('n'), - (int) $occurrence + (int) $selectedOccurrence )->modify($this->startTime()); } @@ -544,9 +545,9 @@ protected function nextYearly(): void $currentDayOfMonth = $this->currentDate->format('j'); // No sub-rules, so we just advance by year - if (empty($this->byMonth)) { + if (!$this->byMonth) { // Unless it was a leap day! - if (2 == $currentMonth && 29 == $currentDayOfMonth) { + if (2 === (int) $currentMonth && 29 === (int) $currentDayOfMonth) { $counter = 0; do { ++$counter; @@ -560,7 +561,7 @@ protected function nextYearly(): void // functions instead. $nextDate = clone $this->currentDate; $nextDate = $nextDate->modify('+ '.($this->interval * $counter).' years'); - } while (2 != $nextDate->format('n')); + } while (2 !== (int) $nextDate->format('n')); $this->currentDate = $nextDate; @@ -631,7 +632,7 @@ protected function nextYearly(): void $date = $date->sub(new \DateInterval('P'.abs($byYearDay + 1).'D')); } - if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) { + if ($date > $this->currentDate && in_array((int) $date->format('N'), $dayOffsets, true)) { $checkDates[] = $date; } } @@ -662,7 +663,7 @@ protected function nextYearly(): void // If we got a byDay or getMonthDay filter, we must first expand // further. if ($this->byDay || $this->byMonthDay) { - $occurrence = -1; + $selectedOccurrence = -1; while (true) { $occurrences = $this->getMonthlyOccurrences(); @@ -674,7 +675,8 @@ protected function nextYearly(): void if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) { // only consider byMonth matches, // otherwise, we don't follow RRule correctly - if (in_array($currentMonth, $this->byMonth)) { + if (in_array((string) $currentMonth, $this->byMonth, true)) { + $selectedOccurrence = $occurrence; break 2; } } @@ -690,12 +692,12 @@ protected function nextYearly(): void $currentYear += $this->interval; $currentMonth = 1; } - } while (!in_array($currentMonth, $this->byMonth)); + } while (!in_array((string) $currentMonth, $this->byMonth, true)); $this->currentDate = $this->currentDate->setDate( (int) $currentYear, - (int) $currentMonth, - (int) $currentDayOfMonth + $currentMonth, + $currentDayOfMonth ); // To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply @@ -711,7 +713,7 @@ protected function nextYearly(): void $this->currentDate = $this->currentDate->setDate( (int) $currentYear, (int) $currentMonth, - (int) $occurrence + (int) $selectedOccurrence )->modify($this->startTime()); return; @@ -724,10 +726,10 @@ protected function nextYearly(): void $currentYear += $this->interval; $currentMonth = 1; } - } while (!in_array($currentMonth, $this->byMonth)); + } while (!in_array((string) $currentMonth, $this->byMonth, true)); $this->currentDate = $this->currentDate->setDate( (int) $currentYear, - (int) $currentMonth, + $currentMonth, (int) $currentDayOfMonth )->modify($this->startTime()); } @@ -755,7 +757,8 @@ protected function parseRRule($rrule): void $value = strtolower((string) $value); if (!in_array( $value, - ['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'] + ['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'], + true )) { throw new InvalidDataException('Unknown value for FREQ='.strtoupper($value)); } @@ -817,7 +820,7 @@ protected function parseRRule($rrule): void case 'BYYEARDAY': $this->byYearDay = (array) $value; foreach ($this->byYearDay as $byYearDay) { - if (!is_numeric($byYearDay) || (int) $byYearDay < -366 || 0 == (int) $byYearDay || (int) $byYearDay > 366) { + if (!is_numeric($byYearDay) || (int) $byYearDay < -366 || 0 === (int) $byYearDay || (int) $byYearDay > 366) { throw new InvalidDataException('BYYEARDAY in RRULE must have value(s) from 1 to 366, or -366 to -1!'); } } @@ -826,7 +829,7 @@ protected function parseRRule($rrule): void case 'BYWEEKNO': $this->byWeekNo = (array) $value; foreach ($this->byWeekNo as $byWeekNo) { - if (!is_numeric($byWeekNo) || (int) $byWeekNo < -53 || 0 == (int) $byWeekNo || (int) $byWeekNo > 53) { + if (!is_numeric($byWeekNo) || (int) $byWeekNo < -53 || 0 === (int) $byWeekNo || (int) $byWeekNo > 53) { throw new InvalidDataException('BYWEEKNO in RRULE must have value(s) from 1 to 53, or -53 to -1!'); } } diff --git a/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php b/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php index 7547d574a..5e56b82f1 100644 --- a/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php +++ b/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php @@ -40,9 +40,9 @@ public function find(string $tzid, ?bool $failIfUncertain = false): ?\DateTimeZo try { if ( - in_array($tzid, $tzIdentifiers) + in_array($tzid, $tzIdentifiers, true) || preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches) - || in_array($tzid, $this->getIdentifiersBC()) + || in_array($tzid, $this->getIdentifiersBC(), true) ) { return new \DateTimeZone($tzid); } diff --git a/lib/VCardConverter.php b/lib/VCardConverter.php index c1d41bdb3..39969d558 100644 --- a/lib/VCardConverter.php +++ b/lib/VCardConverter.php @@ -38,10 +38,10 @@ public function convert(Component\VCard $input, int $targetVersion): Component\V return clone $input; } - if (!in_array($inputVersion, [Document::VCARD21, Document::VCARD30, Document::VCARD40])) { + if (!in_array($inputVersion, [Document::VCARD21, Document::VCARD30, Document::VCARD40], true)) { throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data'); } - if (!in_array($targetVersion, [Document::VCARD30, Document::VCARD40])) { + if (!in_array($targetVersion, [Document::VCARD30, Document::VCARD40], true)) { throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version'); } @@ -69,7 +69,7 @@ public function convert(Component\VCard $input, int $targetVersion): Component\V protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, int $targetVersion): void { // Skipping these, those are automatically added. - if (in_array($property->name, ['VERSION', 'PRODID'])) { + if (in_array($property->name, ['VERSION', 'PRODID'], true)) { return; } @@ -93,7 +93,7 @@ protected function convertProperty(Component\VCard $input, Component\VCard $outp ); if (Document::VCARD30 === $targetVersion) { - if ($property instanceof Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'])) { + if ($property instanceof Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'], true)) { /** @var Uri $newProperty */ $newProperty = $this->convertUriToBinary($output, $newProperty); } elseif ($property instanceof Property\VCard\DateAndOrTime) { @@ -111,7 +111,7 @@ protected function convertProperty(Component\VCard $input, Component\VCard $outp $newProperty['X-APPLE-OMIT-YEAR'] = '1604'; } - if ('ANNIVERSARY' == $newProperty->name) { + if ('ANNIVERSARY' === $newProperty->name) { // Microsoft non-standard anniversary $newProperty->name = 'X-ANNIVERSARY'; @@ -149,7 +149,7 @@ protected function convertProperty(Component\VCard $input, Component\VCard $outp } } elseif (Document::VCARD40 === $targetVersion) { // These properties were removed in vCard 4.0 - if (in_array($property->name, ['NAME', 'MAILER', 'LABEL', 'CLASS'])) { + if (in_array($property->name, ['NAME', 'MAILER', 'LABEL', 'CLASS'], true)) { return; } @@ -275,7 +275,8 @@ protected function convertBinaryToUri(Component\VCard $output, Binary $newProper foreach ($parameters['TYPE']->getParts() as $typePart) { if (in_array( strtoupper((string) $typePart), - ['JPEG', 'PNG', 'GIF'] + ['JPEG', 'PNG', 'GIF'], + true )) { $mimeType = 'image/'.strtolower((string) $typePart); } else { @@ -328,7 +329,7 @@ protected function convertUriToBinary(Component\VCard $output, Uri $newProperty) $mimeType = substr((string) $value, 5, strpos((string) $value, ',') - 5); if (strpos($mimeType, ';')) { $mimeType = substr($mimeType, 0, strpos($mimeType, ';')); - $newProperty->setValue(base64_decode(substr((string) $value, strpos((string) $value, ',') + 1))); + $newProperty->setValue(base64_decode(substr((string) $value, strpos((string) $value, ',') + 1), true)); } else { $newProperty->setValue(substr((string) $value, strpos((string) $value, ',') + 1)); } @@ -413,7 +414,7 @@ protected function convertParameters30(Property $newProperty, array $parameters) * Any other PREF numbers we'll drop. */ case 'PREF': - if ('1' == $param->getValue()) { + if ('1' === (string) $param->getValue()) { $newProperty->add('TYPE', 'PREF'); } break; From 7d93a3f9aaf3dcfd6a1684f96a64c07aeaa2e169 Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Sun, 14 Jun 2026 18:51:47 +0930 Subject: [PATCH 7/9] test: fix static calls to use self --- tests/VObject/CliTest.php | 4 ++-- tests/VObject/Component/VAvailabilityTest.php | 2 +- tests/VObject/Splitter/ICalendarTest.php | 2 +- tests/VObject/TimeZoneUtilTest.php | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/VObject/CliTest.php b/tests/VObject/CliTest.php index 4b3671778..5240bdd63 100644 --- a/tests/VObject/CliTest.php +++ b/tests/VObject/CliTest.php @@ -154,7 +154,7 @@ public function testConvertJson(): void public function testConvertJCardPretty(): void { if (version_compare(PHP_VERSION, '5.4.0') < 0) { - $this::markTestSkipped('This test required PHP 5.4.0'); + self::markTestSkipped('This test required PHP 5.4.0'); } $inputStream = fopen('php://memory', 'r+'); @@ -306,7 +306,7 @@ public function testConvertDefaultFormats($inputFilename, $outputFilename, $form } self::assertNotNull($triggeredWarning, 'A PHP warning was expected but never triggered.'); - $this::assertStringContainsString( + self::assertStringContainsString( "fopen($inputFilename): Failed to open stream: No such file or directory", $triggeredWarning ); diff --git a/tests/VObject/Component/VAvailabilityTest.php b/tests/VObject/Component/VAvailabilityTest.php index e47653910..d8040f851 100644 --- a/tests/VObject/Component/VAvailabilityTest.php +++ b/tests/VObject/Component/VAvailabilityTest.php @@ -412,7 +412,7 @@ protected function assertIsValid(VObject\Document $document): void $validationResult = $document->validate(); if ($validationResult) { $messages = array_map(fn ($item) => $item['message'], $validationResult); - $this::fail('Failed to assert that the supplied document is a valid document. Validation messages: '.implode(', ', $messages)); + self::fail('Failed to assert that the supplied document is a valid document. Validation messages: '.implode(', ', $messages)); } self::assertEmpty($document->validate()); } diff --git a/tests/VObject/Splitter/ICalendarTest.php b/tests/VObject/Splitter/ICalendarTest.php index 775496dc5..be913780a 100644 --- a/tests/VObject/Splitter/ICalendarTest.php +++ b/tests/VObject/Splitter/ICalendarTest.php @@ -173,7 +173,7 @@ public function testICalendarImportEventWithoutUID(): void fn ($item) => $item['message'], $messages ); - $this::fail('Validation errors: '.implode("\n", $messages)); + self::fail('Validation errors: '.implode("\n", $messages)); } else { self::assertEquals([], $messages); } diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index fa704b459..9bbf0b0c4 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -21,7 +21,7 @@ public function testCorrectTZ(string $timezoneName): void self::assertInstanceOf('DateTimeZone', $tz); } catch (\Exception $e) { if (str_contains($e->getMessage(), 'Unknown or bad timezone')) { - $this::markTestSkipped($timezoneName.' is not (yet) supported in this PHP version. Update pecl/timezonedb'); + self::markTestSkipped($timezoneName.' is not (yet) supported in this PHP version. Update pecl/timezonedb'); } else { throw $e; } @@ -188,7 +188,7 @@ public function testTimeZoneBCIdentifiers(string $tzid): void */ $versionOfPHP = \phpversion(); if ((('8.1.14' === $versionOfPHP) || ('8.2.1' === $versionOfPHP)) && \str_contains($tzid, '+')) { - $this::markTestSkipped("Timezone ids containing '+' do not work on PHP $versionOfPHP"); + self::markTestSkipped("Timezone ids containing '+' do not work on PHP $versionOfPHP"); } $tz = TimeZoneUtil::getTimeZone($tzid); $ex = new \DateTimeZone($tzid); From 0d7d1340310eee34edb4ef587b4fe4dd1ceac23f Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Sun, 14 Jun 2026 19:06:52 +0930 Subject: [PATCH 8/9] chore: simplify calls that used call_user_func_array --- lib/Document.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Document.php b/lib/Document.php index c8fd99054..7cdb65d4f 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -118,10 +118,10 @@ public function getDocumentType(): int public function create(string $name) { if (isset(static::$componentMap[strtoupper($name)])) { - return call_user_func_array($this->createComponent(...), func_get_args()); + return $this->createComponent(...func_get_args()); } - return call_user_func_array($this->createProperty(...), func_get_args()); + return $this->createProperty(...func_get_args()); } /** From 013f8bbc9c7000378895120aa894be4c989dbd93 Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Mon, 15 Jun 2026 12:05:16 +0930 Subject: [PATCH 9/9] test: check result of preg_match_all in PHPUnitAssertions --- lib/PHPUnitAssertions.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/PHPUnitAssertions.php b/lib/PHPUnitAssertions.php index ebf9956c6..aad5438a5 100644 --- a/lib/PHPUnitAssertions.php +++ b/lib/PHPUnitAssertions.php @@ -41,7 +41,7 @@ public function assertVObjectEqualsVObject($expected, $actual, string $message = $input = Reader::read($input); } if (!$input instanceof Component) { - $this::fail('Input must be a string, stream or VObject component'); + self::fail('Input must be a string, stream or VObject component'); } unset($input->PRODID); if ($input instanceof Component\VCalendar && 'GREGORIAN' === (string) $input->CALSCALE) { @@ -58,7 +58,9 @@ public function assertVObjectEqualsVObject($expected, $actual, string $message = $actualSerialized = $getObj($actual)->serialize(); // Finding wildcards in expected. - preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', $expectedSerialized, $matches, PREG_SET_ORDER); + $result = preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', $expectedSerialized, $matches, PREG_SET_ORDER); + + self::assertNotFalse($result); foreach ($matches as $match) { $actualSerialized = preg_replace( @@ -68,7 +70,7 @@ public function assertVObjectEqualsVObject($expected, $actual, string $message = ); } - $this::assertEquals( + self::assertEquals( $expectedSerialized, $actualSerialized, $message