You write an extension, the file lints clean, and installing the application dies:
Fatal error: Declaration of IPS\myapp\extensions\core\FileStorage\Audio::move(?int $offset = null): void must be compatible with IPS\Extensions\FileStorageAbstract::move(int $offset, int $storageConfiguration, ?int $oldConfiguration = null): void
Extension methods are declared by an abstract class, and PHP enforces the
signature exactly. This is not a warning you can ship past — the application will
not install at all, and php -l will not catch it because the file
itself is syntactically fine.
Do not guess. Read the abstract, or copy a working one
The signatures are not what you would design. Two examples that catch people:
/* FileStorage - THREE arguments, and the extra two matter */
public function move( int $offset, int $storageConfiguration, int $oldConfiguration=NULL ): void
/* EditorLocations - SIX arguments, including the attachment array and a viewOnly flag */
public function attachmentPermissionCheck( Member $member, ?int $id1, ?int $id2, ?string $id3,
array $attachment, bool $viewOnly=FALSE ): bool
/* and its lookup returns a union you would not have written */
public function attachmentLookup( ?int $id1=NULL, ?int $id2=NULL, ?string $id3=NULL ): Model|Content|Url|Member|null
Two reliable ways to get them right, both faster than guessing:
# the authority grep -nE "abstract public function" system/Extensions/FileStorageAbstract.php # a working implementation to copy the shape from ls applications/*/extensions/core/FileStorage/*.php sed -n '/function move/,/^\t}/p' applications/blog/extensions/core/FileStorage/Blogs.php
Redeclaring a typed property is the same trap, one level down
This one is worse, because it does not fail on install. It fails the moment the class is first loaded, which may be long after you shipped:
/* fatal: must be array (as in class IPS\Patterns\ActiveRecord) */ protected static $multitons = array(); /* correct */ protected static array $multitons = array();
An ActiveRecord subclass with the untyped version installs happily, passes a lint, builds into a tar, and dies the first time anything actually uses the model. It was found here by seeding data for a screenshot — nothing before that had touched the class.
Some extensions are required, not optional
An Editor form field needs a matching EditorLocations extension.
There is no fallback: build the form without one and IPS throws. The field's
app and key do not create it for you.
$form->add( new Editor( 'my_notes', NULL, FALSE, array(
'app' => 'myapp', 'key' => 'Episodes', 'autoSaveKey' => 'myapp-episode',
) ) );
/* requires applications/myapp/extensions/core/EditorLocations/Episodes.php
AND an entry in data/extensions.json */
Why this is worth a checklist rather than a memory
Every one of these fails in a way that points somewhere unhelpful. The
FileStorage message names the abstract, which is fair enough. The
$multitons one names the parent class and gives you no hint that the
fix is a single word. The missing EditorLocations throws from inside the form
helper, several frames from anything you wrote.
So before writing any extension: open the abstract, or open an existing
implementation in applications/*/extensions/. It takes a minute and
it replaces an install-time fatal with nothing at all.
Related application: Podcasts — Podcasts stores episode audio through Invision Community's own file storage, so it follows whatever you have configured under System, Files - including S3-compatible object storage.
Recommended Comments