Arquitetura
Visão técnica do Mapas Culturais: stack, estrutura de diretórios, abstrações centrais e padrões de projeto.
O Mapas Culturais é uma aplicação web em PHP 8.3 voltada a mapeamento cultural. Ela é construída sobre Slim 4 como microframework HTTP, Doctrine ORM 2 para acesso a dados e PostgreSQL com PostGIS para dados geoespaciais. Os assets de frontend são gerenciados com pnpm workspaces.
Stack
| Layer | Technology |
|---|---|
| Language | PHP 8.3 (declare(strict_types=1)) |
| HTTP framework | Slim 4 |
| ORM | Doctrine ORM 2.16 |
| Database | PostgreSQL + PostGIS |
| Cache | Redis (configurable) |
| Frontend build | pnpm workspaces |
| Symfony Mailer | |
| Logging | Monolog |
| Validation | Respect Validation |
Estrutura de diretórios
src/
core/ → MapasCulturais\ (framework base classes)
modules/ → MapasCulturais\Modules\ (modular features)
themes/ → MapasCulturais\Themes\ (installation themes)
conf/ → configuration loader
tools/ → CLI tools (apply-updates.php, psysh.php)
config/ → base configuration (PHP arrays)
dev/config.d/ → local development overrides
public/ → web root
tests/ → PHPUnit test suite
helm/ → Helm chart for Kubernetes
docker/ → Docker configuration and entrypointPSR-4 namespaces
| Namespace | Directory |
|---|---|
MapasCulturais\ | src/core/ |
MapasCulturais\Modules\ | src/modules/ |
MapasCulturais\Themes\ | src/themes/ |
Tests\ | tests/ |
Core abstractions
App singleton
The application is accessed as a singleton via App::i(). It holds every top-level service:
$app = MapasCulturais\App::i();
$app->em; // Doctrine EntityManager
$app->hooks; // Hook/event system
$app->view; // Active theme instance
$app->auth; // Authentication provider
$app->cache; // Persistent cache (Redis)
$app->log; // Monolog loggerThe singleton pattern means you can retrieve the same application instance from anywhere in the codebase without passing dependencies manually.
Entity
MapasCulturais\Entity is the abstract base class for all Doctrine-managed objects. Entities use PHP 8 attributes for ORM mapping:
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'agent')]
class Agent extends MapasCulturais\Entity
{
// ...
}All entities share a common set of status constants:
| Constant | Value | Meaning |
|---|---|---|
Entity::STATUS_ENABLED | 1 | Published and visible |
Entity::STATUS_DRAFT | 0 | Saved but not published |
Entity::STATUS_ARCHIVED | -2 | Archived |
Entity::STATUS_DISABLED | -9 | Disabled by an admin |
Entity::STATUS_TRASH | -10 | Soft-deleted (trash) |
Entity instances expose permission checking through canUser() and checkPermission():
// Returns true/false
$agent->canUser('modify');
// Throws PermissionDenied if user cannot perform the action
$agent->checkPermission('remove');Controller
MapasCulturais\Controller is the abstract base for all route handlers. Action methods are named after the HTTP method they handle:
class MyController extends MapasCulturais\Controller
{
// Responds to GET /my-controller/index
public function GET_index(): void
{
$this->render('my-controller/index', ['data' => []]);
}
// Responds to any HTTP method
public function ALL_create(): void
{
$this->requireAuthentication();
$this->json(['status' => 'ok']);
}
}Register a controller in the application:
$app->registerController('my-controller', MyController::class);Inside action methods, request data is available through:
$this->data— merged URL params +$_REQUEST$this->getData,$this->postData,$this->putData
Hooks
Hooks are the primary extension mechanism. They let modules and themes react to named events throughout the request lifecycle:
$app->hook('entity(Agent).save:before', function() {
// $this is the Agent entity being saved
});See the hooks reference for the full naming syntax and available events.
Database migrations
Migrations live in src/db-updates.php and are applied automatically on container startup via docker/entrypoint.sh. You can also apply them manually:
./scripts/db-update.shThere is no migration versioning tool — each entry in db-updates.php is tracked by a hash and only applied once.
Common request flows
The legacy developer guide spent more time on how requests move across the stack. The three flows below are the ones worth keeping in your mental model when debugging.
Authentication flow
This is why auth bugs often span configuration, provider code, theme-level logout UI, and controller behavior at the same time.
Entity creation flow
If an entity "saves but behaves strangely", inspect hooks and async jobs in addition to the controller itself.
Search flow
Search bugs frequently come from filter syntax, metadata visibility, or geospatial assumptions rather than from a broken endpoint.
Configuration
Configuration is merged from multiple sources at runtime:
config/ — PHP array files with defaults for all settings.
Each theme can contribute its own configuration values through conf-base.php inside the theme directory.
dev/config.d/ — local override files loaded in alphabetical order. The active theme, debug flags, and local credentials go here.
Key variables: DB_HOST, DB_NAME, DB_USER, DB_PASS, REDIS_CACHE, APP_DEBUG, BASE_URL.
Authentication
Authentication providers are pluggable. The active provider is set in configuration and is accessed at runtime through $app->auth. Providers implement the MapasCulturais\AuthProvider interface.
Data layer notes
The data layer is more than "Doctrine over PostgreSQL":
- PostgreSQL stores the canonical application state
- PostGIS supports map-aware queries such as proximity filters
- metadata tables extend base entities without schema changes for every new field
- Redis can back cache and session storage when enabled
That combination explains a common maintenance pattern: schema issues, metadata issues, and geospatial issues may surface in the same user-facing feature.
Error handling
Use typed exceptions from MapasCulturais\Exceptions\:
| Exception | When to use |
|---|---|
PermissionDenied | User lacks the required permission |
NotFound | Entity or resource does not exist |
WorkflowRequest | Invalid state transition in a workflow |
Key design patterns
Singleton
App is a singleton accessed via App::i(). Ensures a single shared instance of all services.
Repository (Doctrine)
Entities are queried through Doctrine repositories: $app->repo('Agent')->find($id).
Hook / Event system
Modules and themes extend behavior by registering callbacks on named hooks rather than modifying core files.
Frontend build
Frontend assets are organized as pnpm workspaces under src/:
# Install all workspace dependencies
dev/pnpm.sh -C src install
# Production build
dev/pnpm.sh -C src run build
# Development build with source maps
dev/pnpm.sh -C src run dev
# Watch mode
dev/pnpm.sh -C src run watchWorkspace packages: src/modules/*, src/plugins/*, src/themes/*, src/node_scripts.
Frontend builds must run inside the Docker container or via the dev/pnpm.sh wrapper, which proxies commands into the container where Node.js and pnpm are available.
Esse material é fruto do Programa de Difusão Nacional - Funarte Redes das Artes, realizado pelo Laboratório do Futuro (entidade vinculada à Universidade Federal do Ceará) no ano de 2025.