Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a97cb34
chore: support PHP 8.2 and up
phil-davis Jun 1, 2026
7de87c7
test: fix unit test things
phil-davis Jun 11, 2026
5a4792f
fix: throw InvalidDataException if Cli tool cannot open input file
phil-davis Jun 14, 2026
38acfdf
test: fix PHPUnitAssertions to pass phpstan
phil-davis Jun 14, 2026
67504c4
test: adjust test code to pass PHPstan
phil-davis Jun 14, 2026
2eca60c
fix: adjust code so that phpstan passes
phil-davis Jun 14, 2026
7d93a3f
test: fix static calls to use self
phil-davis Jun 14, 2026
0d7d134
chore: simplify calls that used call_user_func_array
phil-davis Jun 14, 2026
013f8bb
test: check result of preg_match_all in PHPUnitAssertions
phil-davis Jun 15, 2026
10ca60f
Merge pull request #767 from phil-davis/drop-php7-20260611
phil-davis Jun 15, 2026
fb1aa15
ci: pin GitHub actions to SHA
phil-davis Jun 15, 2026
684e35e
Merge pull request #768 from phil-davis/pin-actions
phil-davis Jun 15, 2026
55cb62c
fix: minor code fixes
phil-davis Jun 15, 2026
0961a0a
Merge pull request #769 from phil-davis/minor-code-20260615
phil-davis Jun 15, 2026
cdabfd0
test: convert DataProvider annotation to attribute
phil-davis Jun 28, 2026
0089050
test: AddParamArrayDocblockFromDataProviderRector
phil-davis Jun 28, 2026
6afc39c
test: import DataProvider class to tests
phil-davis Jun 28, 2026
6ffa024
Merge pull request #772 from phil-davis/adjust-test-code
phil-davis Jun 28, 2026
c61f57f
chore(deps): bump actions/cache from 5.0.5 to 6.1.0
dependabot[bot] Jul 1, 2026
8d81b94
chore(deps): bump actions/checkout from 6.0.3 to 7.0.0
dependabot[bot] Jul 1, 2026
5ab2c27
chore(deps): bump shivammathur/setup-php from 2.37.1 to 2.37.2
dependabot[bot] Jul 1, 2026
81ba1f8
Merge pull request #773 from sabre-io/dependabot/github_actions/actio…
phil-davis Jul 2, 2026
4e05941
Merge pull request #774 from sabre-io/dependabot/github_actions/actio…
phil-davis Jul 2, 2026
b2905cf
Merge pull request #775 from sabre-io/dependabot/github_actions/shiva…
phil-davis Jul 2, 2026
04c0bc4
feat: preserve UTC 'Z' indicator in Period JSON serialization
guimard Oct 14, 2025
e60341e
Merge pull request #765 from bastien-roucaries/411
phil-davis Jul 7, 2026
af78936
chore: prepare release 4.6.1
phil-davis Jul 7, 2026
15dfcb7
Merge pull request #778 from phil-davis/forward-port-4.6.1-to-master
phil-davis Jul 7, 2026
fb5863d
chore: prepare major release 5.0.0
phil-davis Jun 15, 2026
2533eb0
Merge pull request #770 from phil-davis/prepare-5.0.0
phil-davis Jul 7, 2026
a6e164c
Merge upstream sabre-io/vobject 5.0.0
Sep 30, 2026
32c942b
chore: drop deprecated AddParamArrayDocblockFromDataProviderRector fr…
phil-davis Aug 2, 2026
3473176
chore: apply rector IfToNullCoalescingAssignRector rule
phil-davis Sep 4, 2026
3b084ae
Adapt Proton code to 5.x tooling
Sep 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,28 @@ 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
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.37.2
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, dom, fileinfo, mysql, redis, opcache
Expand All @@ -42,7 +45,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache composer dependencies
uses: actions/cache@v5
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
Expand All @@ -61,9 +64,13 @@ 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

- name: Code Coverage
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
if: matrix.coverage != 'none'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ vendor/
composer.lock
tests/cov/
tests/temp
tests/.phpunit.cache
tests/.phpunit.result.cache

# Development stuff
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,23 @@ ChangeLog
* #3 Add daily occurrences to nextMonth and NextYear
* #4 Enhance fast forward speed if no count value has been given

5.0.0 (2026-07-07)
------------------
* #767 Drop PHP 7.4 8.0 8.1 and support PHP 8.2 and up (@phil-davis)
* #769 fix: minor code fixes (@phil-davis)
* #586 PHP types (@tcitworld @phil-davis)

This major release v5 declares function parameter and return types across the
codebase. Users will need to be careful to call functions/methods with the
correct types.

Support for PHP below 8.2 has been dropped.
This release supports PHP 8.2 and up.

4.6.1 (2026-07-07)
------------------
* #776 fix: preserve UTC 'Z' indicator in Period JSON serialization (@bastien-roucaries @guimard @phil-davis)

4.6.0 (2026-05-31)
------------------
* #718 feat(VCard): add getByTypes method (@JimKnoxx)
Expand Down
28 changes: 23 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.6"
},
"suggest" : {
"hoa/bench" : "If you would like to run the benchmark scripts"
Expand Down Expand Up @@ -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"
],
Expand All @@ -104,5 +114,13 @@
"composer cs-fixer",
"composer phpunit"
]
},
"config": {
"allow-plugins": {
"phpstan/extension-installer": true
},
"platform": {
"php": "8.2"
}
}
}
4 changes: 2 additions & 2 deletions lib/BirthdayCalendarGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand Down
80 changes: 25 additions & 55 deletions lib/Cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -181,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) {
Expand All @@ -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';
Expand All @@ -219,12 +185,13 @@ public function main(array $argv): int

try {
while ($input = $this->readInput()) {
// @phpstan-ignore method.dynamicName
$returnCode = $this->$command($input);
if (0 !== $returnCode) {
$realCode = $returnCode;
}
}
} catch (EofException $e) {
} catch (EofException) {
// end of file
} catch (\Exception $e) {
$this->log('Error: '.$e->getMessage(), 'red');
Expand Down Expand Up @@ -408,7 +375,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;
Expand Down Expand Up @@ -538,7 +505,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;
Expand Down Expand Up @@ -593,17 +560,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 {
Expand All @@ -628,6 +595,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) {
Expand Down
16 changes: 7 additions & 9 deletions lib/Component.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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
);
}

Expand Down Expand Up @@ -368,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) {
Expand All @@ -378,7 +376,7 @@ public function xmlSerialize(Xml\Writer $writer): void
$writer->endElement();
}

if (!empty($components)) {
if ([] !== $components) {
$writer->startElement('components');

foreach ($components as $component) {
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions lib/Component/VAlarm.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ 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';
$related = (isset($trigger['RELATED']) && 'END' === strtoupper($trigger['RELATED'])) ? 'END' : 'START';

/** @var VEvent|VTodo $parentComponent */
$parentComponent = $this->parent;
Expand Down
Loading
Loading