Skip to content

Contributing and running the tests

4 min read

Basehim is a deliberately small, dependency-free PHP CMS, and contributions that keep it that way are welcome. This page gets you from a fresh clone to a passing test run and a pull request.

Before you start

  • Bugs and small fixes: open a pull request directly.
  • New features, dependencies or schema changes: open an issue first, so the shape can be agreed before you spend time on it.
  • Security problems: report them privately, never in a public issue. See Security for developers.

Set up a checkout

git clone https://github.com/<your-username>/basehim.git
cd basehim
php -S localhost:8000 server.php      # open http://localhost:8000 and run the installer

Set APP_DEBUG=true in the generated .env while you work. To start over, drop the database and delete .env.

The test suite

The runner and its assertions are a few hundred lines in tests/support/. There's no Composer and no PHPUnit.

php tests/run.php                       # unit tests: no database needed
php tests/run.php --filter=csrf         # only tests whose name contains "csrf"
php tests/run.php --keep                # keep the test database and logs afterwards

Integration tests

Integration tests drive the real index.php over HTTP, with real sessions, the real login form and a real database. They run when you point the runner at a database server:

BASEHIM_TEST_DB_HOST=127.0.0.1 \
BASEHIM_TEST_DB_USER=root BASEHIM_TEST_DB_PASS=secret \
php tests/run.php
VariableDefault
BASEHIM_TEST_DB_HOSTnoneRequired to enable integration tests
BASEHIM_TEST_DB_PORT3306
BASEHIM_TEST_DB_USER, BASEHIM_TEST_DB_PASSroot, empty
BASEHIM_TEST_DB_NAMEbasehim_testMust end in _test: it is dropped and recreated
BASEHIM_TEST_DB_PREFIXbht_Exercises table prefixing

A run creates the database, applies every migration, seeds a user for each role, installs a test app, and serves the site with PHP's built-in server. It never reads your own .env or touches your own database. With Node.js 18+ installed, the JavaScript client tests run too.

CI runs the whole suite against MySQL 8.0 on PHP 8.1 and 8.3, on every push and pull request.

Writing a test

<?php
// tests/Integration/MediaOwnershipTest.php

test("an author cannot rename someone else's image", function () {
    $b = bh_browser('author');                     // signed in through the real login form
    $res = $b->ajax('media.update', ['id' => 1, 'title' => 'x']);
    assert_same(403, $res->status, $res->body);
    assert_same('FORBIDDEN', $res->errorCode());
});

Test files end in Test.php and are found automatically.

  • Tests in tests/Unit/ must not need a database.
  • Tests in tests/Integration/ may use bh_browser($role), bh_db(), bh_env(), bh_core_log() and bh_app_log().
  • Assertions: assert_same, assert_true, assert_false, assert_contains, assert_not_contains and assert_throws. Each takes an optional message.
  • Name a test after what it proves, in a sentence. A failing test should read like a bug report.

Add a test for every bug you fix, and check that it fails without your fix.

Code conventions

  • declare(strict_types=1); at the top of every PHP file.
  • PSR-12, four-space indentation, LF line endings.
  • PSR-4: App\ maps to app/, and the file name matches the class.
  • Classes are final unless they are designed for extension.
  • Repositories do data access, services do business logic, controllers stay thin.
  • A short docblock on every class, and on any method whose behaviour isn't obvious. Explain why, not what.
  • PHP 8.1 is the minimum. Don't use syntax from later versions.

Check syntax before pushing:

find . -name "*.php" -not -path "./vendor/*" -print0 | xargs -0 -n1 php -l

Database changes

Add a new numbered file to database/migrations/. Never edit one that has shipped, because existing sites have already run it.

  • Use {table} tokens so DB_PREFIX works.
  • Make every statement work on MySQL 5.7+ and MariaDB 10.3+.
  • For conditional changes, check information_schema and use PREPARE/EXECUTE instead of MariaDB-only syntax such as ADD COLUMN IF NOT EXISTS.

See Updates, migrations and backups.

Compatibility

Basehim runs on cPanel-style shared hosting, and that shapes what can be accepted:

  • no Composer packages, build steps, daemons or shell access at runtime;
  • no new PHP extensions beyond the ones the installer checks;
  • public behaviour (REST responses, hooks, App methods, theme variables, URLs) changes only with a compatibility plan and a changelog entry.

Pull requests

  1. Branch from main: git checkout -b fix/short-description.
  2. Keep the change to one concern.
  3. Run php tests/run.php with a test database, and add tests.
  4. Update the docs if behaviour changed.
  5. Write a commit message that explains the reasoning, in a loose conventional style:
fix(router): resolve subdirectory base path on HEAD requests
feat(api): add cursor pagination to /api/v1/posts
docs(themes): document the widgets.php field format

Keep building

More guides, or jump straight into the API reference.

Discussion

Leave a comment