Contributing and running the tests
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
| Variable | Default | |
|---|---|---|
BASEHIM_TEST_DB_HOST | none | Required to enable integration tests |
BASEHIM_TEST_DB_PORT | 3306 | |
BASEHIM_TEST_DB_USER, BASEHIM_TEST_DB_PASS | root, empty | |
BASEHIM_TEST_DB_NAME | basehim_test | Must end in _test: it is dropped and recreated |
BASEHIM_TEST_DB_PREFIX | bht_ | 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 usebh_browser($role),bh_db(),bh_env(),bh_core_log()andbh_app_log(). - Assertions:
assert_same,assert_true,assert_false,assert_contains,assert_not_containsandassert_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 toapp/, and the file name matches the class. - Classes are
finalunless 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 soDB_PREFIXworks. - Make every statement work on MySQL 5.7+ and MariaDB 10.3+.
- For conditional changes, check
information_schemaand usePREPARE/EXECUTEinstead of MariaDB-only syntax such asADD 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,
Appmethods, theme variables, URLs) changes only with a compatibility plan and a changelog entry.
Pull requests
- Branch from
main:git checkout -b fix/short-description. - Keep the change to one concern.
- Run
php tests/run.phpwith a test database, and add tests. - Update the docs if behaviour changed.
- 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
Leave a comment