Skip to content

Add add-function.php to scaffold new bridge functions#4

Open
sonalidudhia wants to merge 1 commit into
MrPunyapal:mainfrom
sonalidudhia:feat/add-function-scaffolder
Open

Add add-function.php to scaffold new bridge functions#4
sonalidudhia wants to merge 1 commit into
MrPunyapal:mainfrom
sonalidudhia:feat/add-function-scaffolder

Conversation

@sonalidudhia

@sonalidudhia sonalidudhia commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Problem

Adding one bridge function today means hand-editing six files across three languages — nativephp.json, the Contract, Plugin, the Facade, the Kotlin class, the Swift class — plus a test. Miss one and it doesn't fail until runtime, on a real device. The stubs/ directory has sat unused since the template shipped, reserved for "future scaffolding automation" per the README.

The one-liner

$ php add-function.php --name=Level --description="Reads the battery level."

NativePHP Bridge Function Scaffolder
Add a new bridge function to an already configured plugin.

Bridge function added: Battery.Level

Files changed:
- nativephp.json
- src/Contracts/BatteryContract.php
- src/Plugin.php
- src/Facades/Battery.php
- resources/android/BatteryFunctions.kt
- resources/ios/BatteryFunctions.swift
- tests/LevelTest.php

Next steps:
1. Implement the native logic in resources/android/*.kt (com.acme.mobile-battery.BatteryFunctions.Level).
2. Implement the native logic in resources/ios/*.swift (BatteryFunctions.Level).
3. Run composer test.

Flags

Flag Effect
--name=Level Required. PascalCase bridge function name.
--description="..." Optional, stored in the nativephp.json entry.
--skip-android Skip the Kotlin file and omit the manifest android key.
--skip-ios Skip the Swift file and omit the manifest ios key.
--dry-run Print planned changes; write nothing.
--no-interaction Don't prompt; useful for CI/automation.

How it works (no baked-in coupling)

configure.php runs once, inside the raw template, and deletes itself. add-function.php ships alongside it but runs after configure, repeatedly, inside the developer's already-configured plugin — so it can't have configure-time placeholders baked in. Instead it discovers everything at runtime: the bridge namespace from nativephp.json, the PHP namespace from composer.json's autoload.psr-4, the Contract/Facade files by scanning src/Contracts and src/Facades, and the native bridge files by globbing resources/android/resources/ios. It reuses the existing stubs/bridge-function-android.stub and stubs/bridge-function-ios.stub (extracting the nested-class block via brace-counting and splicing it into the existing bridge file) and adds one new stub, stubs/bridge-function-test.stub, for the generated Pest test.

Validates before writing anything: PascalCase-only names, duplicate detection (checked against both nativephp.json and the Contract, since they can drift), and rejection of names that collide with PHP, Kotlin, or Swift reserved words. If a native bridge file or its insertion anchor can't be found, that one file is skipped with a warning and everything else still completes — nothing hard-fails on a missing platform file.

Verification

  • Replicated CI's configure command, then ran the scaffolder against the configured copy.
  • php -l on every touched file; manually checked the Kotlin/Swift output for balanced braces and structure matching Example.
  • Three functions in a row (Level, ChargeState, TemperatureCelsius) → composer test still green (catches insertion-anchor drift across repeated runs).
  • Re-running the same --name → fails with the duplicate message, git status clean (verified before and after — zero writes on failure).
  • --name=list → rejected (not PascalCase). --name=Print → rejected (PHP reserved word).
  • --dry-run → prints the plan, git status clean.
  • --skip-android / --skip-ios → manifest omits the corresponding key, that platform's file untouched.
  • Deleted resources/android entirely and re-ran → warns, skips only that file, still finishes PHP + iOS + test + exits 0.
  • Ran on the unconfigured template → aborts with "run configure.php first"; all original placeholders intact (grep -rn '{{ ' clean except the {{ function }}/{{ function_camel }} family the scaffolder itself uses, which doesn't collide with configure's replacement keys).
  • composer test / composer lint (Pint + PHPStan level 8 + Rector dry-run + composer validate) on the generated Contract/Plugin/Facade/test code — clean as generated. (Pint's pre-existing failures on Plugin.php, Manifest.php, etc. are unrelated — confirmed identical on main before this branch touches anything, since composer.lock isn't committed and a newer laravel/pint resolves fresh with stricter rules than the repo was authored against.)

Also touched

  • tests/ManifestTest.php — relaxed the hardcoded toHaveCount(1) assertion. It broke the instant a second function was added; it now looks up the Example entry by name instead of asserting the whole array's size, so it still verifies what it originally verified without breaking on legitimate growth.
  • configure.php — one defensive line so add-function.php is never in its placeholder-rewrite scan (belt-and-suspenders; it was already safe in practice since none of the placeholder keys collide, but this removes any doubt).
  • README.md, docs/creating-first-plugin.md, docs/bridge-functions.md — replaced the "future automation" wording with the actual command and a flag table; docs/creating-first-plugin.md now leads with add-function.php and keeps the manual six-step walkthrough as the "what it does under the hood" explanation.

For maintainer input

  • Marker comments vs. brace-anchored insertion. This PR anchors on brace-counting from the last class {{ function }} line in the stub / last } in the target file, rather than adding // nativephp:functions-style markers. It's simpler and needed no changes to existing files, but it's more brittle if someone hand-edits the bridge files into an unusual shape. Happy to switch to markers if you'd rather have the more robust (but more invasive) contract.
  • Should this live in the template at all, or eventually move to a separate nativephp-plugin-maker-style tool? Kept it here since it's zero-dependency and the stubs already lived in this repo unused.
  • Reserved-word policy. The PHP/Kotlin/Swift reserved-word lists are pragmatic subsets, not exhaustive — cited inline in add-function.php. Open to trimming or growing them.

Scope was kept tight: no event scaffolding via stubs/event.stub, no function-removal command — both listed here as explicit follow-ups rather than folded into this diff.

Summary by CodeRabbit

  • New Features

    • Added a command-line tool to scaffold bridge functions across PHP, Android, iOS, configuration, facades, and tests.
    • Added validation for naming conflicts and unsupported names.
    • Added --dry-run, --skip-android, --skip-ios, and non-interactive options.
    • Platform-specific changes now warn and continue when automatic insertion is unavailable.
  • Documentation

    • Expanded bridge-function and plugin setup guides with usage examples, generated artifacts, flags, and troubleshooting details.
  • Tests

    • Added reusable bridge-function test scaffolding and improved manifest validation coverage.

Adding one bridge function today means hand-editing six files across
three languages (nativephp.json, Contract, Plugin, Facade, Kotlin,
Swift) plus a test — miss one and it only fails at runtime on a
device. add-function.php runs after configure.php, inside an already
configured plugin, and does all of it in one command:

    php add-function.php --name=Level --description="Reads the battery level."

Everything it needs is discovered at runtime (nativephp.json,
composer.json's psr-4 map, the single Contract/Facade files, the
resources/android and resources/ios bridge files) so the script has
no baked-in coupling to any one plugin and configure.php never has to
touch it (confirmed: configure.php's placeholder map doesn't collide
with the {{ function }}/{{ function_camel }} family the scaffolder
uses, plus a one-line defensive skip added to configure.php anyway).

Validates before writing anything: PascalCase names only, rejects
duplicates (checked against nativephp.json and the contract), rejects
PHP/Kotlin/Swift reserved words. --skip-android/--skip-ios omit that
platform's file and manifest key. --dry-run previews with zero writes.
If a native bridge file or insertion anchor can't be found, that one
file is skipped with a warning and the rest still completes.

Also: relaxed tests/ManifestTest.php's hardcoded bridge_functions
count assertion, since it broke the instant a second function was
added — it now checks the Example entry specifically instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@sonalidudhia sonalidudhia marked this pull request as draft July 10, 2026 11:15
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds add-function.php, a CLI scaffolder that updates bridge manifests, PHP sources, native bridge files, and tests. It adds validation, dry-run and platform-skip options, updates configuration behavior, documents the workflow, and revises manifest assertions.

Changes

Bridge Function Scaffolding

Layer / File(s) Summary
CLI discovery and validation
add-function.php, configure.php
The CLI parses options, discovers namespaces and target files, validates names and duplicates, derives platform targets, and prevents configuration from copying add-function.php.
Manifest and PHP artifact generation
add-function.php
The scaffolder appends manifest data, inserts contract and plugin methods, updates facade annotations, and generates a PHP test from a stub.
Native bridge generation
add-function.php
Kotlin and Swift classes are extracted from stubs and inserted into native bridge files, with warnings for manual platform handling.
Workflow documentation and validation
README.md, docs/*.md, stubs/bridge-function-test.stub, tests/ManifestTest.php
Documentation describes the CLI workflow and flags, the test stub verifies bridge forwarding, and manifest assertions locate the Example entry by name.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Developer
  participant FunctionScaffolder
  participant Manifest
  participant PHPSource
  participant NativeBridge
  participant GeneratedTest
  Developer->>FunctionScaffolder: run add-function.php with options
  FunctionScaffolder->>Manifest: append bridge function entry
  FunctionScaffolder->>PHPSource: insert contract, plugin, and facade updates
  FunctionScaffolder->>NativeBridge: insert Kotlin and Swift bridge classes
  FunctionScaffolder->>GeneratedTest: render bridge-function test
  FunctionScaffolder-->>Developer: print touched files and warnings
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding add-function.php to scaffold bridge functions.
Description check ✅ Passed The description is detailed and covers the PR's problem, usage, flags, verification, and placeholder safety, though it doesn't follow the template headings exactly.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@sonalidudhia sonalidudhia marked this pull request as ready for review July 10, 2026 11:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@add-function.php`:
- Around line 203-211: The write loop in the scaffolding command ignores mkdir()
and file_put_contents() failures, allowing partial plugin generation. Update the
foreach block that processes $writes to validate directory creation and each
file write, abort on any error, and roll back already-created artifacts or stage
writes atomically before reporting success.
- Around line 227-239: Normalize the Swift reserved-word comparison in
validateReservedWords so mixed-case names such as Type and Protocol are
rejected. Either lowercase every self::SWIFT_RESERVED entry or perform a
case-insensitive comparison while preserving the existing skipIos behavior.

In `@docs/creating-first-plugin.md`:
- Line 6: Update the plugin creation step referencing `add-function.php` to say
that each command adds the required function artifacts, rather than replacing
the template `Example` function; mention that the existing `Example` artifacts
must be removed separately if applicable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fae823c1-3b64-4528-962e-9d6c1d4c22ed

📥 Commits

Reviewing files that changed from the base of the PR and between 1991914 and a8bed11.

📒 Files selected for processing (7)
  • README.md
  • add-function.php
  • configure.php
  • docs/bridge-functions.md
  • docs/creating-first-plugin.md
  • stubs/bridge-function-test.stub
  • tests/ManifestTest.php

Comment thread add-function.php
Comment on lines +203 to +211
foreach ($writes as $path => $contents) {
$directory = dirname($path);

if (! is_dir($directory)) {
mkdir($directory, 0777, true);
}

file_put_contents($path, $contents);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Prevent partial scaffolding writes from leaving an inconsistent plugin.

mkdir() and file_put_contents() failures are ignored, so the command can report success after writing only some artifacts. A failure while writing the manifest, PHP files, or test can leave the repository in a mixed state. Check every operation and preferably stage/rollback the complete set of files before reporting success.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@add-function.php` around lines 203 - 211, The write loop in the scaffolding
command ignores mkdir() and file_put_contents() failures, allowing partial
plugin generation. Update the foreach block that processes $writes to validate
directory creation and each file write, abort on any error, and roll back
already-created artifacts or stage writes atomically before reporting success.

Comment thread add-function.php
Comment on lines +227 to +239
private function validateReservedWords(string $methodName, string $name, bool $skipAndroid, bool $skipIos): void
{
if (in_array(strtolower($methodName), self::PHP_RESERVED, true)) {
$this->fail("[{$name}] camelCases to the PHP reserved word [{$methodName}] and cannot be used as a method name. Choose a different name.");
}

if (! $skipAndroid && in_array(strtolower($name), self::KOTLIN_RESERVED, true)) {
$this->fail("[{$name}] collides with a Kotlin reserved word. Choose a different name or pass --skip-android.");
}

if (! $skipIos && in_array(strtolower($name), self::SWIFT_RESERVED, true)) {
$this->fail("[{$name}] collides with a Swift reserved word. Choose a different name or pass --skip-ios.");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file and inspect the relevant sections with line numbers.
git ls-files | rg '(^|/)add-function\.php$|(^|/)AddFunction\.php$|(^|/)add-function\.php$'
echo '---'
# Show the file if found via a common path search
fd -a 'add-function.php|AddFunction.php' . 2>/dev/null || true
echo '---'
# Search for the validator and reserved-word constants/usages
rg -n "validateReservedWords|PHP_RESERVED|KOTLIN_RESERVED|SWIFT_RESERVED" .

Repository: MrPunyapal/nativephp-plugin-template

Length of output: 975


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the constants and the validator context.
sed -n '1,90p' add-function.php
echo '---'
sed -n '110,255p' add-function.php
echo '---'
# Search for where the method name and name are derived/normalized.
rg -n "camel|methodName|strtolower\\(|ucfirst|reserved" add-function.php

Repository: MrPunyapal/nativephp-plugin-template

Length of output: 12636


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
swift_reserved = [
    'associatedtype', 'class', 'deinit', 'enum', 'extension', 'fileprivate',
    'func', 'import', 'init', 'inout', 'internal', 'let', 'open', 'operator',
    'private', 'protocol', 'public', 'rethrows', 'static', 'struct',
    'subscript', 'typealias', 'var', 'self', 'Self', 'Type', 'Protocol',
]

candidates = ['Self', 'self', 'Protocol', 'protocol', 'Type', 'type', 'TYPE']
for name in candidates:
    blocked = name.lower() in swift_reserved
    print(f"{name:10} -> {'blocked' if blocked else 'bypasses'}")
PY

Repository: MrPunyapal/nativephp-plugin-template

Length of output: 331


Normalize the Swift reserved-word check add-function.php:237

strtolower($name) still lets Type and Protocol through because self::SWIFT_RESERVED mixes lowercase and capitalized entries. Lowercase the reserved list too, or compare case-insensitively, so those names are rejected as intended.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@add-function.php` around lines 227 - 239, Normalize the Swift reserved-word
comparison in validateReservedWords so mixed-case names such as Type and
Protocol are rejected. Either lowercase every self::SWIFT_RESERVED entry or
perform a case-insensitive comparison while preserving the existing skipIos
behavior.

3. Review `composer.json`, `nativephp.json`, PHP namespaces, and native bridge targets.
4. Replace the template bridge function body with platform code.
5. Run tests and static analysis.
4. Run `php add-function.php --name=Level --description="Reads the battery level."` for each bridge function your plugin needs, replacing the template `Example` function with your own.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Do not describe scaffolding as replacing the Example function.

add-function.php appends the new manifest entry, methods, native classes, and test; it does not remove the existing Example artifacts. Update this step to say “add” or document the separate cleanup required for Example.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/creating-first-plugin.md` at line 6, Update the plugin creation step
referencing `add-function.php` to say that each command adds the required
function artifacts, rather than replacing the template `Example` function;
mention that the existing `Example` artifacts must be removed separately if
applicable.

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.

1 participant