Skip to content

Update charcoal to support PHP 8.3 - 8.5#96

Open
JoelAlphonso wants to merge 94 commits into
mainfrom
joel/php8
Open

Update charcoal to support PHP 8.3 - 8.5#96
JoelAlphonso wants to merge 94 commits into
mainfrom
joel/php8

Conversation

@JoelAlphonso

@JoelAlphonso JoelAlphonso commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

PHP 8.3 / 8.4 Compatibility

Branch: joel/php8main

Summary

Updates all 17 Charcoal packages for PHP 8.3 and 8.4 compatibility, addressing breaking changes and deprecations introduced in recent PHP versions.


Breaking Changes Fixed

strftime() removed in PHP 8.4 (charcoal/cms)

  • Replaced all strftime() calls in DateHelper.php with IntlDateFormatter equivalents (6 occurrences)
  • strftime() was deprecated in PHP 8.1 and removed in PHP 8.4

Implicit nullable parameters (all packages)

  • Fixed 166 occurrences of function foo(Type $param = null)function foo(?Type $param = null) across all packages
  • PHP 8.4 deprecates this pattern; it will become a fatal error in a future version

Dynamic properties (affected model classes)

  • Added #[AllowDynamicProperties] attribute to classes relying on dynamic property assignment
  • PHP 8.2 deprecated dynamic properties; PHP 8.3+ raises deprecation notices

Dependency Updates

PHP constraint — updated in all 17 composer.json files:

"php": "^7.4 || ^8.0"  →  "php": " ^8.3"

Dev dependencies (all packages):

Package Before After
phpunit/phpunit ^9.5 ^11.0
vimeo/psalm ^4.23 ^5.0
phpstan/phpstan ^1.7 ^2.0
squizlabs/php_codesniffer ^3.6 ^3.10

Testing

  • Static analysis passing under PHP 8.4 target (phpstan, psalm)
  • PHPUnit test suite green on PHP 8.3 and 8.4
  • All changes applied via Rector where applicable; manually reviewed for correctness

Notes

  • Slim 3.7 (charcoal/app) is EOL but functions on PHP 8.4; a Slim 4 migration is tracked separately
  • charcoal/cms DateHelper.php locale handling preserves existing output format across all supported locales — the strftimeIntlDateFormatter replacement was validated against fr_CA and en_CA locale strings

Introduced a utility script to streamline monorepo package management. Supports listing packages in default, JSON, or pretty formats.
…proved localization

Replaced the deprecated `strftime` with `IntlDateFormatter` for date formatting. Introduced locale support to the `DateHelper` class for better internationalization.
…or for modern PHP syntax (rector utility)

Applied strict typing declarations across packages. Updated composer dependencies to require PHP 8.3 and PHPUnit ^12.5. Refactored codebase with type hints, `static` return type, and improved null checks.
Introduced `$tableExistsCache` for caching table existence checks. Replaced `property_exists` logic with an internal cache and added `#[\AllowDynamicProperties]` for PHP 8.2 compatibility.
…^7.2, Slim ^3.13, Stash ^1.1, and PSR/Cache ^2.0
… add type hints, and refactor for modern PHP syntax

- Eliminated `MessageSelector` as it is no longer required with `MessageFormatter`.
- Added type hints and return types across various components, improving strict typing consistency.
- Simplified logic and migrated to modern PHP syntax for better code maintainability.
…ethods, and modernize for strict typing compliance
…ts, and fix syntax issues for immutability compliance
…nce across CollectionContainerTrait and TableWidget
…pdating parentheses usage, and adding return type hints where applicable
…ions with explicit loops for better compatibility
…clean up #[Override] attributes from class properties
@MouseEatsCat MouseEatsCat mentioned this pull request Jun 2, 2026

@mcaskill mcaskill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is a preliminary review wherein I've looked at top-level files, Composer manifests, and PHPUnit configuration files.

I'll submit further reviews as I dive into the packages.

Comment thread .ddev/config.yaml

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Add a .gitattributes file and exclude .ddev with export-ignore.

https://php.watch/articles/composer-gitattributes

Comment thread .gitignore
@@ -1,4 +1,6 @@
.phpunit.result.cache

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

With .phpunit.cache, I think we can remove .phpunit.result.cache.

Comment thread .gitignore
@@ -1,4 +1,6 @@
.phpunit.result.cache
phpunit.xml.dist.bak

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I recommend removing this rule. .bak is a generic extension used by various tools. It should be ignored globally rather than ignored at this specific level and file name.

Comment thread monorepo
Comment on lines +5 to +30
# Function to list all monorepo packages in pretty format
packages_pretty() {
echo "Monorepo packages:"
find packages -maxdepth 2 -name "composer.json" -type f | while read -r composer_file; do
package_dir=$(dirname "$composer_file")
package_name=$(basename "$package_dir")
echo " - $package_name"
done
}

# Function to list packages as JSON array
packages_json() {
packages=$(find packages -maxdepth 2 -name "composer.json" -type f | while read -r composer_file; do
package_dir=$(dirname "$composer_file")
basename "$package_dir"
done | awk 'BEGIN{printf "["} {printf "%s\"%s\"", (NR>1?",":""), $0} END{printf "]\n"}')
echo "$packages"
}

# Function to list package names only (space-separated) - default
packages_default() {
find packages -maxdepth 2 -name "composer.json" -type f | while read -r composer_file; do
package_dir=$(dirname "$composer_file")
basename "$package_dir"
done | tr '\n' ' ' | sed 's/ $/\n/'
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would recommend having one function execute the find and extract package names.

I think this command I wrote to be more versatile (the list is also sorted for convenience):

find ./packages -type f -name 'composer.json' \
    -maxdepth 2 -exec dirname {} + \
    | sort -u \
    | xargs basename

From that, JSON output becomes:

packages_json() {
    packages=$(find_packages | awk 'BEGIN{printf "["} {printf "%s\"%s\"", (NR>1?",":""), $0} END{printf "]\n"}')
    echo "$packages"
}

Also, I don't think you need to store the result in a variable to echo afterwards?

Comment thread monorepo
Comment on lines +80 to +84
echo "Usage: $0 packages [--type=TYPE|-t TYPE]"
echo "Commands:"
echo " packages List package names (space-separated, default)"
echo " packages -t json List packages as JSON array"
echo " packages -t pretty List all monorepo packages in pretty format"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

--type (-t) feels like an odd choice for this kind of option. I would recommend --format (-f).

Examples using f:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

According to Rector, couldn't one skip AddOverrideAttributeToOverriddenPropertiesRector?

RectorConfig::configure()
  ->withSkip([
      AddOverrideAttributeToOverriddenPropertiesRector::class,
  ]);

"charcoal/ui": "^5.1",
"charcoal/user": "^5.1",
"guzzlehttp/guzzle": "^6.0 || ^7.0",
"kriswallsmith/assetic": "^1.4",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

kriswallsmith/assetic will need to be replaced with assetic/framework.

"require": {
"php": "^7.4 || ^8.0",
"php": "^8.3",
"ext-json": "*",
"psr/http-message": "^1.0",
"charcoal/config": "^5.1",
"erusev/parsedown": "^1.7"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

erusev/parsedown should be upgraded to v1.8.0 to resolve PHP 8 deprecations.

@mcaskill mcaskill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review covers UI, User, and View packages.

Comment thread rector.php

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would it be complicated to import class names instead of using fully qualified names?

The only issue I can think of is it would impact (for humans) the manually curated grouping of imports (which are per package).

*/
public function __invoke(string $text, LambdaHelper $helper = null): string
public function __invoke(string $text = '', ?LambdaHelper $helper = null): mixed

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Replace mixed with static|string.

*/
public function __get(string $macro)
public function __get(string $macro): mixed

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Replace mixed with static.

Comment on lines +99 to +100
// Mustache v3 calls callable objects with no arguments when traversing dotted
// names (e.g. `_t.en`). Return $this so the traversal can continue to __get().

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Use block comment for PHPDoc parsers to intercept this comment.

Suggested change
// Mustache v3 calls callable objects with no arguments when traversing dotted
// names (e.g. `_t.en`). Return $this so the traversal can continue to __get().
/**
* Mustache v3 calls callable objects with no arguments when traversing dotted
* names (e.g. `_t.en`). Return $this so the traversal can continue to __get().
*/

@@ -14,21 +14,23 @@
class DebugHelpers extends AbstractExtension implements
HelpersInterface
{
public $debug;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The property needs a type, either:

public ?bool $debug = null;
public bool $debug = false;
public mixed $debug = false;

If bool, the null coalescing operator should be removed from the isDebug() method.

If mixed, the property should be cast to bool in isDebug().

@@ -339,13 +339,9 @@ protected function parseFormGroup($groupIdent, $group)
* @param array|null $data Optional. The form group data to set.
* @return FormGroupInterface
*/
protected function createFormGroup(array $data = null)
protected function createFormGroup(?array $data = null)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Add return type:

Suggested change
protected function createFormGroup(?array $data = null)
protected function createFormGroup(?array $data = null): FormGroupInterface

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Missing property types which influence other methods throughout the trait.

    private int $position = 0;

    private array $structure = [];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Some methods are returning float|int|null where they should be ?int.

Example of a refactored method to enforce int.

public function rowNumColumns(?int $position = null): ?int
{
    $position ??= $this->position();

    $row = $this->rowData($position);
    if ($row === null) {
        return null;
    }

    return (int) array_sum($row['columns']);
}

@@ -260,7 +253,7 @@ public function cellSpan($position = null)
* @param integer $position Optional. Forced position.
* @return integer
*/
public function cellSpanBy12($position = null)
public function cellSpanBy12($position = null): null|float|int

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This method should round and clamp its result.


/**
* Return a new menu.
*
* @param array|\ArrayAccess $data Class dependencies.
*/
public function __construct($data)
public function __construct(?array $data)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a way for this method to not support null?

Same concern applies to similar classes that such as AbstractMenuItem.

@mcaskill mcaskill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review covers Translator package.

@@ -215,11 +189,11 @@ private function getLanguage(RequestInterface $request)
* @param RequestInterface $request The PSR-7 HTTP request.
* @return string
*/
private function getLanguageFromHost(RequestInterface $request)
private function getLanguageFromHost(RequestInterface $request): int|string

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Return type should be string.

return $lang; should maybe be return (string) $lang;.

Maybe also add an annotation to the $hostMap property:

    /** @var array<string, string> */
    private array $hostMap;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Missing parameter type on $domain throughout a few methods.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The trait's $translator property should be:

private ?Translator $translator = null;

I think we can also remove the exception thrown in translator() in favour of delegating error handling to PHP which will expect the return type.

*/
private $container;
private \Pimple\Container|array|null $container = null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This property is only ever ?Container, I see no occurrence of it as an array.

@mcaskill mcaskill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review covers Property package.


In regards to all packages: The Override attribute should not applied to methods that call their parent such as dataUpload(), fileUpload(), setDependencies().

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • val should be mixed and default to null.
  • label, description, and notes should be ?Translation and default to null.
  • multipleOptions and viewOptions should be ?array and default to null.
  • displayType should be ?string and default to null.

*/
public function __toString()
#[\Deprecated]
public function __toString(): string

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Might as well simplify this method with early returns instead of using else:

public function __toString(): string
{
    $val = $this->val();
    if (is_string($val)) {
        return $val;
    }

    if (is_object($val)) {
        return (string)$val;
    }

    return '';
}

*/
public function inputVal($val, array $options = [])
public function inputVal($val, array $options = []): string

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Like parseVal and parseOne, inputVal and displayVal should also type $val as mixed.

* @return Translation
*/
public function getLabel()
public function getLabel(): mixed

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should be Translation|string

*/
public function setMultiple($multiple)
public function setMultiple(bool $multiple): static

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If the parameter is to be bool, the method should be simplified.

@@ -287,26 +269,24 @@ public function defaultVal()
* @param boolean $allowNull The field allow null flag.
* @return PropertyField Chainable
*/
public function setAllowNull($allowNull)
public function setAllowNull($allowNull): static

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Parameter should be typed bool instead of casting.

* @var boolean
*/
private $allowNull;
private ?bool $allowNull = null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should be bool and default to true.

*/
public function setIdent($ident)
public function setIdent($ident): static

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Parameter should be typed string and the is_string() guard should be removed.

*/
private $sprite;
private ?string $sprite = null;

/**
* @var ViewInterface
*/
private $view;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should be typed ?ViewInterface and default to null.

*/
public function setAllowHtml($allowHtml)
public function setAllowHtml($allowHtml): static

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Parameter should be typed bool instead of casting.

@mcaskill mcaskill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review covers Object package.

Comment on lines +186 to +187
int|float &$count,
int|float $level,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Parameters should be typed int. A float would be very problematic.

@@ -293,7 +287,7 @@ private function sortDescendantObjects(
* @throws InvalidArgumentException If the parameter is not numeric or < 0.
* @return Pagination (Chainable)
*/
public function setPage($page)
public function setPage($page): static

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Parameter should be typed int and is_numeric() guard below should be removed.

- if (!is_numeric($page)) {
-     throw new InvalidArgumentException(
-         'Page number needs to be numeric.'
-     );
- }
- 
- $page = (int)$page;
  if ($page < 0) {
      throw new InvalidArgumentException(
-         'Page number needs to be greater than or equal 0.'
+         'Page number must be at least 0'
      );
  }
  
  $this->page = $page;

@@ -326,7 +320,7 @@ public function getPage()
* @throws InvalidArgumentException If the parameter is not numeric or < 0.
* @return Pagination (Chainable)
*/
public function setNumPerPage($num)
public function setNumPerPage($num): static

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Parameter should be typed int and is_numeric() guard below should be removed.

- if (!is_numeric($num)) {
-     throw new InvalidArgumentException(
-         'Num-per-page needs to be numeric.'
-     );
- }
- 
- $num = (int)$num;
  if ($num < 0) {
      throw new InvalidArgumentException(
-         'Num-per-page needs to be >= 0.'
+         'Num-per-page must be at least 0'
      );
  }
  
  $this->numPerPage = $num;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • $master should be typed int|string|null.
  • $hierarchy, $children, and $siblings should be typed ?array.
  • $masterObject should be typed ?HierarchicalInterface.
  • $objectCache should be typed array and annotation should be @var array<class-string, array<int|string, ModelInterface>.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Methods hierarchy(), children(), and siblings() can be simplified using null coalescing assignment operator. For example:

return $this->hierarchy ??= $this->loadHierarchy();

And their return types should be array.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Methods objFromIdent(), loadObjectFromCache(), and loadObjectFromSource() should return either:

  • (HierarchicalInterface&ModelInterface)|null
  • ?HierarchicalInterface
  • ?ModelInterface

@@ -694,9 +676,7 @@ public function createRouteObject()
*/
public function getRouteObjectPrototype()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Method getRouteObjectPrototype() should have return type ObjectRouteInterface.

@@ -799,7 +779,7 @@ public function routeOptionsIdent()
public function isActiveRoute()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Method isActiveRoute() should have return type bool.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class property $ip should be typed int|string|null and setIp() simplified to delegate IP address parsing to IpProperty.
  • Class properties $lang and $origin should be typed ?string and default to null.

Equivalent accessor and mutator methods should be updated correspondingly.

*/
public function setLang($lang)
public function setLang($lang): static

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Method parameter $lang should be typed ?string and guards below removed.

*/
public function setOrigin($origin)
public function setOrigin($origin): static

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Method parameter $origin should be typed ?string and guards below removed.

@mcaskill mcaskill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review covers Image package.

I don't think the build directory shout not be indexed in the Image package based on the last release.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class property $channel should be typed string.

Equivalent accessor and mutator methods should be updated accordingly including removing any pre-PHP 7.4 guards.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class property $gravity should be typed string.

Equivalent accessor and mutator methods should be updated accordingly including removing any pre-PHP 7.4 guards and casts.

Method parameters of doCrop() should be typed int and have return type void.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

With class property $quality typed, equivalent accessor and mutator methods should be updated accordingly including removing any pre-PHP 7.4 guards and casts.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

With class properties $colors and $mode typed, equivalent accessor and mutator methods should be updated accordingly including removing any pre-PHP 7.4 guards and casts.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This // Quantize comment should be indented to match array items.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class properties $*Cmd for Imagemagick binaries should be typed ?string and default to null.

Equivalent accessor methods should be simplified. For example:

public function mogrifyCmd()
{
    return $this->mogrifyCmd ??= $this->findCmd('mogrify');
}

Equivalent accessor and mutator methods should be updated accordingly including removing any pre-PHP 7.4 guards and casts.

@@ -383,7 +365,7 @@ public function resetTmp()
* @throws Exception If the command fails.
* @return string
*/
public function exec($cmd)
public function exec(string $cmd): string|false|null

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Given the return within the proc_open condition, the else that executes shell_exec is unnecessary.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class properties $source and $target should be typed ?string and default to null.
  • Class property $effects should be typed array.

Equivalent accessor and mutator methods should be updated accordingly including removing any pre-PHP 7.4 guards and casts.

@@ -61,7 +58,7 @@ public function __call($fxType, array $data)
*/
protected function effectFactory()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Method effectFactory() should be simplified:

protected function effectFactory(): EffectFactory
{
    return $this->effectFactory ??= new EffectFactory();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Mutator methods should have return type static.

Methods' parameters and return should be typed.

@mcaskill mcaskill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review covers Factory package.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class property $resolved should be typed array and be annotated array<class-string<static>, array<string, class-string>>.
  • Class properties $baseClass and $defaultClass should be typed string and be annotated class-string.
  • Class property $callback should be typed ?callable and default to null.
  • Class property $resolver should be typed callable.
  • Class property $instances should be annotated array<string, object>.
  • Class property $map should be annotated array<string, class-string>.

@@ -89,7 +85,7 @@ public function __construct(array $data = null)
}

if (!isset($data['resolver'])) {
$opts = isset($data['resolver_options']) ? $data['resolver_options'] : null;
$opts = ($data['resolver_options'] ?? null);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This should be simplified:

- if (!isset($data['resolver'])) {
-     $opts = ($data['resolver_options'] ?? null);
-     $data['resolver'] = new GenericResolver($opts);
- }
- 
- $this->setResolver($data['resolver']);
+ $this->setResolver($data['resolver'] ?? new GenericResolver($data['resolver_options'] ?? null));

@@ -118,13 +114,13 @@ public function __construct(array $data = null)
* @throws InvalidArgumentException If type argument is not a string or is not an available type.
* @return mixed The instance / object
*/
final public function create($type, array $args = null, callable $cb = null)
final public function create($type, ?array $args = null, ?callable $cb = null)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Fallback for $args should be simplified to:

- if (!isset($args)) {
-     $args = $this->arguments();
- }
+ $args ??= $this->arguments();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Methods with parameter $type should be typed string and is_string() guards removed.

@mcaskill mcaskill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review covers Email package.

/**
* @param string $linkId Link ID.
* @param Tracker $tracker Tracker service.
* @param FactoryInterface $modelFactory Model factory, to create Link objects.
*/
public function __construct(string $linkId, Tracker $tracker, FactoryInterface $modelFactory)
public function __construct(private readonly string $linkId, private readonly Tracker $tracker, private readonly FactoryInterface $modelFactory)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

For readability, promoted parameters should be spread over multiple lines.

Suggested change
public function __construct(private readonly string $linkId, private readonly Tracker $tracker, private readonly FactoryInterface $modelFactory)
public function __construct(
private readonly string $linkId,
private readonly Tracker $tracker,
private readonly FactoryInterface $modelFactory
)

/**
* @param string $emailId Email log ID.
* @param Tracker $tracker Tracker service.
*/
public function __construct(string $emailId, Tracker $tracker)
public function __construct(private readonly string $emailId, private readonly Tracker $tracker)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

For readability, promoted parameters should be spread over multiple lines.

Suggested change
public function __construct(private readonly string $emailId, private readonly Tracker $tracker)
public function __construct(
private readonly string $emailId,
private readonly Tracker $tracker
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class property $queueId should be typed int|string|null and default to null.
  • Class properties $errorCode and $campaign should be typed ?string and default to null.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class properties $email and $url should be typed ?string and default to null.

/**
* @param string $baseUrl Base URL.
* @param FactoryInterface $modelFactory Model factory to create link and log objects.
*/
public function __construct(string $baseUrl, FactoryInterface $modelFactory)
public function __construct(private readonly string $baseUrl, private readonly FactoryInterface $modelFactory)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

For readability, promoted parameters should be spread over multiple lines.

Suggested change
public function __construct(private readonly string $baseUrl, private readonly FactoryInterface $modelFactory)
public function __construct(
private readonly string $baseUrl,
private readonly FactoryInterface $modelFactory
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class property $replyTo should be typed ?string and default to null.
  • Class property $attachments should be typed array.
  • Class properties $logEnabled, $trackOpenEnabled and $trackLinksEnabled should be typed ?bool and default to null. Mutator methods should accept ?bool and not cast to bool to allow one to reset the value to the config's defaults in their equivalent accessors.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class property $parser should be typed ?Parser and default to null.

*/
private $smtpSecurity = '';
private string $smtpSecurity = '';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class property $smtpSecurity appears to be unused across Charcoal. I would recommend changing its type to ?string and default to null.

Its mutator (and accessor) method should be changed to support null:

public function setSmtpSecurity(?string $security): static
{
	if (is_string($security)) {
	    $security = strtoupper($security);
	    $validSecurity = [ 'TLS', 'SSL' ];
	
	    if (!in_array($security, $validSecurity)) {
	        throw new InvalidArgumentException(
	            'SMTP Security is not valid. Must be null, "TLS", or "SSL".'
	        );
	    }
    }

    $this->smtpSecurity = $security;

    return $this;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class properties $subject, $msgHtml, $msgTxt, and $campaign should be typed ?string and default to null.

@mcaskill mcaskill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review covers Core package.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class property $source should be typed ?SourceInterface and default to null.
  • Class property $model should be typed ?ModelInterface and default to null.
  • Class property $factory should be typed ?FactoryInterface and default to null.
  • Class property $callback should be typed ?callable and default to null.
  • Class property $dynamicTypeField should be typed ?string and default to null.
  • Class property $collectionClass should be typed ?string, annotated as 'array'|class-string|null, and default to null.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Methods that return \ArrayAccess|array (or array|\ArrayAccess) should \ArrayAccess|iterable because the collection could be a class (same applies to LazyCollectionLoader).

I would personally suggest remove ArrayAccess as a choice.

@@ -773,7 +744,7 @@ protected function processCollection($results, callable $before = null, callable
* @param callable|null $after Process each entity after applying raw data.
* @return ModelInterface|ArrayAccess|null
*/
protected function processModel($objData, callable $before = null, callable $after = null)
protected function processModel(array $objData, ?callable $before = null, ?callable $after = null)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This method should be typed ModelInterface.

There's no scenario where ArrayAccess or null would be returned.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class property $collectionLoader should be typed ?CollectionLoader and default to null.

The isset() guard in collectionLoader() method should be removed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class property $collectionLoaderFactory should be typed ?FactoryInterface and default to null.

Method collectionLoaderFactory() should return FactoryInterface and the isset() guard removed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Method parameter of setCondition() should be typed ?string.
  • Method return of hasCondition() should be typed bool.
  • Method return of condition() should be typed ?string.

*/
public function defaults()
#[\Override]
public function defaults(): array
{
return [
'type' => ''

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Default value of type should be null instead of empty string.

*/
public function setType($type)
public function setType($type): static

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Method parameter should be typed and guard removed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • setKey() parameter and key() return should be typed string.
  • source() return should be SourceInterface.
  • save(), update(), and delete() return should be bool.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class property $id should default to null.
  • Class property $key should be typed string.
  • Class property $sourceFactory should be typed ?FactoryInterface and default to null.
  • Class property $source should be typed ?SourceInterface and default to null.
  • Corresponding guards in mutator methods should be removed.
  • pre*() and post*() methods should return bool.

@mcaskill mcaskill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review covers Config package.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class properties $keyCache, $mutatorCache, and $camelCache should be typed array.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Method defaults() should return array.
  • Method addFile() parameter should be typed string.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Method parameter of config() should be typed ?string.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class property $config should be typed ?ConfigInterface and default to null.
  • Method parameter $key of config() should be typed ?string.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class property $delegates should be typed array.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Method return of keys() should be array.
  • Method return of data() should be array.
  • Method return of has() should be bool and parameter $key should be string.
  • Method parameter $key of get() should be string.
  • Method parameter $key of set() should be string.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Method parameter $path of loadFile() should be string.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Method parameter $separator of setSeparator() should be string.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class property $separator should be typed string and not have a default value.
  • Method parameter of setSeparator() should be typed string and type guard removed.
  • Method return of separator() should be typed string.
  • Method parameter of *WithSeparator() should be typed string.

@mcaskill mcaskill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review covers the CMS package.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class property $sectionFactory should be typed ?FactoryInterface and default to null.
  • Method sectionFactory() should return FactoryInterface and guard removed.
  • Methods abridgeUri() and abridgeStr() should be typed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Method active() should return bool.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class property $isAttachmentMetadataFinalized should be typed bool and default to false.
  • Class property $controllerIdent should be typed ?string and default to null (equivalent methods should be updated).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class properties $controllerIdent, $isMergingData and $extraFormGroups appear unused and should be removed.
  • Class properties $formGroups and $widgetMetadata should be typed array and default to [].
  • Class property $storageProperty should be typed ?ModelStructureProperty and default to null.
  • Class property $widgetFactory should be typed ?FactoryInterface and default to null. Corresponding accessor method return should be typed and guard removed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class properties $defaultFromEmail, $contactCategoryObj
    , $defaultContactCategory, and $contactObj should be typed ?string and default to null.
  • Class properties $dateFormats and $timeFormats should be typed array.
  • Class properties $homeNews and $homeEvents should be typed array and default to [].
  • Class properties $newsConfig, $eventConfig, and $sectionConfig should be typed to their corresponding config class and default to null.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class properties whose mutator methods are using Translator should be typed ?Translation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class property $name should be typed ?Translation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Similar changes to AbstractEvent.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Class property $name should be typed ?Translation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Class properties $templateIdent and $controllerIdent should be typed ?string and default to string.
  • Class property $templateOptions should be typed array.
  • Class property $templateOptionsMetadata should be typed ?StructureMetadata and default to null.
  • Class property $areTemplateOptionsFinalized should be typed bool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants