Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
55 changes: 39 additions & 16 deletions docs/development/plugin-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -636,11 +636,15 @@ A page is a page because it is in `src/Pages/`; core lists that directory —
buckets. Anything else you ship is simply autoloaded when something names it.

Bare spellings still work wherever **core** does the resolving:
`getClass('HelloWorld')`, `FOGController::getManager()`, `FOGPage::$childClass`,
`Route::_newEntity()` and `Authorization`'s object-scope lookup all take a
short name. Core builds that map from the file *paths* now rather than by
reading every plugin file, so you never spell the namespace out anywhere except
the `namespace` declaration itself.
`FOGController::getManager()`, `FOGPage::$childClass`, `Route::_newEntity()`,
`Authorization`'s object-scope lookup and a variable handed to `getClass()` all
take a short name. Core builds that map from the file *paths* now rather than
by reading every plugin file, so you never spell the namespace out anywhere
except the `namespace` declaration itself.

That is about names core is *given*. Names **you** write are a different
question: spell those fully qualified rather than routing them through
`getClass()`, per ADR 0043 and the section below.

**`class_alias()` is not needed and should not be used.** It was the workaround
for a gap that no longer exists — discovery once derived a bare class name from
Expand Down Expand Up @@ -674,17 +678,36 @@ friends. Core wins it: a plugin class whose short name matches a core class is
reachable only by its FQCN. Pick a distinctive name and the question never
arises.

### Name the class, do not fetch it by string

**Write `new \FOG\Plugins\LDAP\Managers\LDAPGroupManager()`, not
`self::getClass('LDAPGroupManager')`** (ADR 0043). The factory erased the
type — it is declared `@return object|mixed` — so nothing could check what you
then did with the result, and it was never a substitution seam: `qualify()`
consults core's map before the plugins', so a bare name can only ever resolve
to one class. Core was converted in the same sweep, and
`tests/getclass-literals.test.php` there refuses a literal `getClass()`.

Fully qualified rather than imported, because the plugin repository's
`tests/core-references-are-qualified.test.php` refuses a bare core name
outright — a plugin tree is fetched on its own and cannot assume core's
class list is anywhere nearby.

`getClass()` itself has not gone and still resolves a bare name through
`FOGBase::qualify()`. Reach for it when the class is named by a **variable**,
which is the one thing `new` cannot express, and for
`getClass('X', '', true)`, which returns the default properties rather than an
instance.

### The one place a bare name still bites

Core resolves a bare name wherever *it* does the resolving: `getClass()`,
`getManager()`, discovery, `$childClass`, `Route::_newEntity()`,
Core resolves a bare name wherever *it* does the resolving: `getClass()` with a
variable, `getManager()`, discovery, `$childClass`, `Route::_newEntity()`,
`Authorization`. A class name **your own code** holds in a plain string is
resolved exactly as written, with no such mapping behind it. So
`self::getClass('LDAPGroupManager')` inside a plugin is fine — core resolves
it — but a raw `new $someString` or `is_subclass_of($x, 'SomeClass')` naming
a bare plugin class in your own code is not. Spell those fully qualified
(`\FOG\Plugins\LDAP\Managers\LDAPGroupManager`) or route them through
`self::getClass()` instead.
resolved exactly as written, with no such mapping behind it — so a raw
`new $someString` or `is_subclass_of($x, 'SomeClass')` naming a bare plugin
class is not. Spell those fully qualified
(`\FOG\Plugins\LDAP\Managers\LDAPGroupManager`).

### What the failure looks like

Expand Down Expand Up @@ -874,7 +897,7 @@ Once your callback has proven who somebody is, hand the identity to FOG and
**say how you proved it**:

```php
$user = self::getClass('User', $uid);
$user = new \FOG\Items\User($uid);
$user->establishSession('oidc');
```

Expand Down Expand Up @@ -944,8 +967,8 @@ just-provisioned account has no id before then.
superglobals) — never raw `$_POST`/`$_GET`.
- **CSRF/auth:** call `self::checkAuthAndCSRF()` at the top of every state-
changing POST handler.
- **Instantiation:** prefer `self::getClass('HelloWorld')` /
`self::getClass('HelloWorldManager')` over `new`.
- **Instantiation:** `new \FOG\Plugins\HelloWorld\Items\HelloWorld()`, fully
qualified. Not `self::getClass('HelloWorld')` — see ADR 0043.
- **Translation:** wrap UI strings in `_('…')`.
- **Secrets in your table:** if a column holds a credential — an API token, a
webhook URL, a bind password — declare it through `API_SENSITIVE_FIELDS` or
Expand Down
2 changes: 1 addition & 1 deletion docs/development/storage-node-selection-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class MyStorageHook extends Hook
$StorageGroup = $arguments['StorageGroup'];

// ...your selection logic, e.g. by subnet, weighting, etc...
$chosen = self::getClass('StorageNode', $someNodeId);
$chosen = new \FOG\Items\StorageNode($someNodeId);

if ($chosen->isValid()) {
$arguments['StorageNode'] = $chosen; // override FOG's choice
Expand Down