Download the PHP package lumnd/platoadmin without Composer
On this page you can find all versions of the php package lumnd/platoadmin. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package platoadmin
PlatoAdmin
English · 中文
A back office scaffold on PlatoPHP, and the reference application for it: everything the framework documentation describes in the abstract is here as code that runs.
Two applications share one tree. admin renders Smarty templates; api is a JSON service whose
controllers are generated from the contracts in api/contracts/. Both sit on the models, services
and language packs under common/. Start a project from it and the parts every back office
rewrites from scratch — accounts, roles, permissions, sessions, logs, menus, settings, scheduled
tasks — are already there.
MIT licensed, like the framework.
Roles — a role is a set of ct:ac permissions |
Accounts — status and two-step state at a glance |
| Operation log — no delete; pruning is by age and is itself logged | Sessions — the authority is the database row, not the cookie |
Screens shown with seeded demo data. More: docs/assets/screenshots/
Try it: https://admin.platophp.com/ — sign in as demo / platoadmin.
The demo signs in to a read-only role, so every screen and every form opens and nothing writes; it resets to the seeded state every half hour. Two behaviours you will meet and which are not faults: a write comes back 403, and opening the form of a role that holds permissions the demo does not is refused, because an account cannot grant what it does not hold itself.
What is in it
Back office (admin)
- Accounts and roles — a role is a set of permissions, written as
ct:ac/ct:*/*. An account cannot grant a permission it does not hold itself. - Sign-in — password plus optional two-step verification, rate limited per source address. An account can be made to change its password on first use, restricted to a set of addresses, or given an expiry date.
- Sessions — the authority is the database row, not the cookie, so any live session can be signed out from the list and a suspension takes effect on the next request.
- Logs — a sign-in log and an operation log, with each operation tied to the session that performed it. Neither has a delete: pruning is by age and is itself logged.
- Menu — a tree, maintained in the back office, whose items line up with controller actions.
- Settings and scheduled tasks — tasks are defined in configuration; the screen can disable and restore one and shows how long each run took and whether it succeeded.
API (api)
- The auth chain:
send_code,login,refresh,logout. - Sign-in by email code, SMS code, Google or Apple. Deliberately no password sign-in.
- The access token is a JWT (HS256, two hours). The refresh token is stored as a sha256 digest, used once, and rotated.
- Controllers and the OpenAPI document are both generated from
api/contracts/. The code a person writes lives inapi/app/logic/.
Shared by both
- One failure mechanism: throw
biz_exception.common\middleware\catcheris the only place that turns an exception into a response — there is no sentinel return value and no error map. - Deterministic field encryption (so an encrypted column can still be matched on equality), soft
deletes, and one response envelope:
{code, msg, data, timestamp}. - Language packs for
zh-cnanden, phpstan level 8, Pest tests.
What is deliberately not in it
Not omissions — decisions, listed so nobody goes looking:
- No product domain. User profiles, content, subscriptions and payment are not settled, so the main tree has neither their tables nor their endpoints.
- The SMS channel is a stub.
common\integration\smssends nothing, and an SMS code request answers honestly that it could not be sent rather than pretending it went out. - No resident server runs. The full profile installs
lumnd/plato-workermanand registers theserver:*commands, but there is noconfig/server.phpand no server implementation. - Two-step verification validates but does not enrol. An account with
otp_enabledis asked for a code at sign-in; the screen that generates a secret and confirms a scan is not written yet. - The Google and Apple adapters have never seen a real token. They are verified as far as refusing a deployment that has no client id configured.
Quick start
With Docker, from nothing to a back office you can sign in to:
Then open http://admin.platoadmin.localhost:8080/. No hosts file entry is needed — the
.localhost suffix is reserved for the loopback address and resolvers answer it on their own.
The compose stack is PHP 8.3-fpm, nginx, MySQL 8 and Redis 7, and it is for development only: the source is a bind mount, opcache is off and the database password is in the compose file in plain sight. docs/running.md covers it properly, including how to use an nginx you run yourself instead.
Requirements
Without Docker, running it yourself needs:
- PHP 8.2 to run it, 8.3 to develop it. 8.2 is the floor
composer.jsonstates and CI proves with acomposer install --no-devjob; the dev toolchain needs 8.3, because Pest does - Composer 2
- MySQL 8.0
- Redis
- Extensions:
pdo_mysql,redis,mbstring,openssl,json,zlib; a resident process also needspcntlandposix
Install
Profiles
The install asks which profile you want, deletes what the answer excludes, and removes itself:
| Profile | What it is |
|---|---|
minimal |
Back office only. No api/, no contract generation, no resident server. The dependencies come down to lumnd/platophp plus smarty/smarty and chillerlan/php-qrcode |
dsl |
Adds api/, where endpoints are declared in api/contracts/ and both the controllers and the OpenAPI document are generated |
full |
Adds resident server support through lumnd/plato-workerman |
Answer up front to skip the question, and add --dry-run to see what would go without touching
anything:
Configure
key:generate fills the three secrets that have to differ in every installation. It only writes
the lines that are empty, so running it twice is safe:
CSRF_SECRET— signs the CSRF cookie. An empty value makes the back office refuse to start.JWT_SECRET— signs api access tokens. Replacing it signs every api client out at once, which is the emergency stop when one leaks.DB_CRYPT_KEY— field encryption. There is no recovery path if it is lost, only re-encryption from a plaintext source, so it must never be swapped out under an environment that already has data.
To replace one that already has a value, name it: key:generate --force=JWT_SECRET, which asks
first. --show prints without writing.
SYS_ENV is one of dev / pre / pub. SYS_DEBUG must be false in production — the debug
panel prints the session and the cookies verbatim.
.env goes in no repository, no image and no release archive. Only .env.example and
.env.testing are committed, and neither holds a real credential.
Run
There is no seeded administrator, and there will not be one. An account committed to a
repository means the password is in the source and every deployment that ran the seeder shares it.
In a container, pass -it to docker exec — without a tty the prompt cannot turn echo off and the
password ends up in the shell history.
The two applications are two document roots: admin/public and api/public. The vhosts are in
deploy/nginx/ — the compose stack mounts that directory straight into nginx, and the same files
copy into an nginx you run yourself. The crontab entry is in deploy/cron/.
CSRF compares Origin against the request host and the cookie is set on that host, so serve it
under a hostname rather than with php -S — 127.0.0.1:8080 works, but it is not the path
production takes. A non-default port is fine: Origin and HTTP_HOST both carry it, so the two
still match.
The full walk-through — the compose stack, bringing your own nginx, the database, the first account, the debug panel and a symptom table — is in docs/running.md.
Layout
| Where | What |
|---|---|
admin/ |
Back office application, entry point admin/public/index.php |
api/ |
API application, entry point api/public/index.php; controllers generated from api/contracts/ |
common/ |
Models, services, support and integrations shared by both, plus the language packs |
database/migrations/ |
Schema and upgrades, the only source of the table structure |
database/seeders/ |
Built-in data (the menu tree). No accounts |
deploy/ |
The nginx vhosts, the crontab entry, and the images the compose stack builds |
docker-compose.yml |
The development stack: PHP 8.3-fpm, nginx, MySQL 8, Redis 7 |
docs/ |
Project documentation |
vendor/lumnd/platophp/ |
The framework, a Composer dependency. Never edited |
What belongs in each directory, and where new code goes, is docs/structure.md. The layering and writing rules are docs/conventions.md.
The framework's own documentation is at https://platophp.com. It is not in vendor/ after a
normal install — the published tags strip docs/ with .gitattributes, so the zipball Composer
unpacks does not carry it. To read it offline, fetch that package from source instead:
which puts it at vendor/lumnd/platophp/docs/, entry point llms.txt.
Commands
Endpoints change in the contract and are then regenerated. Do not hand-edit
api/app/control/ — api/manifest.json will stop you:
Scheduled tasks are driven by a single crontab entry on one host; locally the scheduler does not run by default:
Tests and static analysis:
Documentation
| File | Answers |
|---|---|
| docs/running.md | How to run it, run the tests, and create the first account |
| docs/structure.md | Which directory holds what, and where new code goes |
| docs/conventions.md | Layering, exceptions, error codes, routing and auth, secret handling |
| docs/api-contract.md | How a contract is written, what it generates, which files are hand-written |
| docs/schema.md | Table conventions and the tables that exist |
| docs/frontend.md | Back office front-end conventions |
| docs/storage.md | File storage, uploads and bucket configuration |
| docs/api/openapi.json | The endpoint list, generated from the contracts |
The documentation is written in Chinese and published at
https://lumnd.github.io/platoadmin/, which is the readable copy — it has a sidebar, a pager
and a Swagger UI page for the api. docs/*.md reads fine on GitHub as it is; the same
sources also build into a navigable static site with a Swagger UI page for the api. docs/site/
is not committed — CI regenerates and publishes it on every push, so changing documentation means
changing Markdown, never HTML:
The build needs Node (through npx marked, pinned so two machines produce the same HTML). The
Swagger UI assets are vendored in docs/assets/swagger/ rather than loaded from a CDN.
Working on PlatoAdmin itself
A plain checkout never triggers post-create-project-cmd, so install.php does not run and you
get the whole tree:
CONTRIBUTING.md has the rest: the three gates, what gets a change sent back, and the style and language rules. Security problems go through SECURITY.md rather than the issue tracker.
Three things worth knowing before the first pull request:
- When you develop against a sibling checkout of the framework, do not let the lock file out.
Everything resolves from Packagist normally, so
composer.jsoncarries norepositoriesat all; working on the framework means adding a localtype=pathentry pointing at the sibling checkout. Everyone remembers that line must not be committed, but thecomposer.lockit produces must not be either: thelumnd/*entries get written as"type": "path"withdev-main, which contradicts the~0.1constraint incomposer.jsonand stops somebody else'screate-projectdead withdoes not satisfy your constraint. After removing the path repository, runcomposer update lumnd/platophp lumnd/plato-api-contract lumnd/plato-workermanand check that the lock is back on the tags Packagist resolves before committing it. - CI runs the three gates and builds the documentation.
.github/workflows/ci.ymlruns style, static analysis and the full test suite against a real MySQL and Redis, plus a second job that doescomposer install --no-devon PHP 8.2 so the floor incomposer.jsonis a tested claim..github/workflows/docs.ymlrenders the Markdown to GitHub Pages;.gitlab-ci.ymldoes the same on GitLab Pages. - Every change has to be thought about in three profiles. A new file that only serves the api or
only the resident server has to be registered in
PROFILESininstall.php, or the minimal profile ends up with a file referencing a class that was deleted. Verify all three afterwards, not just the one you touched.
The release process — build an artefact, migrate, move a symlink, restart the processes — is not
implemented. The intended shape is current -> releases/<id> alongside a shared/, with the
production .env written into shared/.env by a secret service or a protected CI variable.
Lines that do not get crossed
- No account, password, token or certificate in plaintext, in the README, in configuration or in a log.
- The
settingtable never holds a secret, a connection string or a third party credential — databases get exported, backed up, and copied into development environments. - Production runs
SYS_ENV=pubandSYS_DEBUG=false. - The framework is a Composer dependency: do not edit
vendor/, the nextcomposer installtakes it away. Framework bugs get fixed in the framework repository.
License
MIT, see LICENSE. PlatoPHP is MIT as well.
All versions of platoadmin with dependencies
lumnd/platophp Version ~0.1
lumnd/plato-api-contract Version ~0.1
lumnd/plato-workerman Version ~0.1
smarty/smarty Version ^5.5
chillerlan/php-qrcode Version ^5.0
firebase/php-jwt Version ^6.10 || ^7.0
phpmailer/phpmailer Version ^6.9
workerman/workerman Version ^4.1