Admin Panel - UI fΓΌr Stammdaten-Verwaltung β
Layer: Dev (UI/Frontend) Purpose: Verwaltung globaler Stammdaten aus allen Layern Framework: Symfony 7.3 + EasyAdmin 4 URL: https://appisym.go4family.net/admin
π― Was ist das Admin Panel? β
Das Admin Panel ist ein Web-UI (EasyAdmin) fΓΌr Administratoren zur Verwaltung von globalen Stammdaten, die das System zum Funktionieren benΓΆtigt.
Wichtig zu verstehen: β
β
Admin Panel = UI/Frontend (EasyAdmin CRUDs)
β
Im Dev Layer (nur Controller)
β
Zeigt Entities aus ALLEN Layern
β
FΓΌr Users mit ROLE_ADMIN
β
Verwaltet globale Daten (Countries, Languages, Industries, etc.)
β NICHT ein separates System
β NICHT mit eigenen Entities
β NICHT im Infrastructure LayerποΈ Architektur - Wie funktioniert es? β
Entities bleiben in ihren Layern! β
Foundation/Geo/
βββ Entity/
β βββ Country.php β Entity bleibt hier!
β βββ Language.php β Entity bleibt hier!
β βββ Currency.php β Entity bleibt hier!
Dev/Admin/Controller/
βββ DashboardController.php β Admin Dashboard
βββ CountryCrudController.php β CRUD fΓΌr Country (zeigt auf Foundation Entity!)
βββ LanguageCrudController.php
βββ CurrencyCrudController.phpDas Admin Panel erstellt NUR die Controller (UI), NICHT die Entities!
π₯ Wer kann das Admin Panel nutzen? β
Admins = Normale Users (aus Identity-System) mit ROLE_ADMIN
// Admins sind Users aus der users-Tabelle
// Mit spezieller Rolle
// User aus Identity-System (Shared Layer)
$user = $userRepository->findById(1);
$user->getRoles(); // ['ROLE_ADMIN']
// Security Check im Admin Panel
#[IsGranted('ROLE_ADMIN')]
class DashboardController extends AbstractDashboardController
{
// Nur Users mit ROLE_ADMIN dΓΌrfen rein
}Keine separate admins-Tabelle! Admins sind Users mit ROLE_ADMIN!
π Dateistruktur - Was gehΓΆrt wohin? β
β RICHTIG - Entities in ihren Layern β
src/Appi/
βββ Foundation/
β βββ Geo/
β βββ Entity/
β β βββ Country.php # Entity hier!
β β βββ Language.php # Entity hier!
β β βββ Currency.php # Entity hier!
β βββ Repository/
β β βββ CountryRepositoryInterface.php
β β βββ CountryRepository.php
β βββ UseCase/
β βββ CreateCountry/
β βββ CreateCountryCommand.php
β βββ CreateCountryHandler.php
β
βββ Core/
β βββ Industry/
β βββ Entity/
β βββ Industry.php # Entity hier!
β
βββ Dev/
βββ Admin/
βββ Controller/
βββ DashboardController.php # Admin Dashboard
βββ CountryCrudController.php # CRUD fΓΌr Country
βββ LanguageCrudController.php # CRUD fΓΌr Language
βββ CurrencyCrudController.php # CRUD fΓΌr Currency
βββ IndustryCrudController.php # CRUD fΓΌr IndustryMigrations bleiben auch in ihren Layern:
migrations/
βββ 3_foundation/
β βββ Version0001_geo.php # Countries, Languages, Currencies hier!
βββ 4_core/
βββ Version0002_industry.php # Industries hier!π§ Workflow: Neues Modul mit Admin-UI β
Beispiel: Geo-Modul (Countries, Languages, Currencies) β
Schritt 1: Entity im richtigen Layer erstellen β
// β
Entity bleibt in Foundation/Geo
// src/Appi/Foundation/Geo/Entity/Country.php
namespace App\Appi\Foundation\Geo\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: CountryRepository::class)]
#[ORM\Table(name: 'countries')]
class Country
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\Column(type: 'string', length: 255)]
private string $name;
#[ORM\Column(type: 'string', length: 2)]
private string $iso2;
#[ORM\Column(type: 'string', length: 3)]
private string $iso3;
// KEIN team_id - globale Daten!
// Getters/Setters
}Schritt 2: Migration im richtigen Layer β
// β
Migration bleibt in Foundation
// migrations/3_foundation/Version0001_geo.php
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version0001_geo extends AbstractMigration
{
public function up(Schema $schema): void
{
// Countries
$this->addSql('CREATE TABLE countries (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
iso2 VARCHAR(2) NOT NULL UNIQUE,
iso3 VARCHAR(3) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)');
// Languages
$this->addSql('CREATE TABLE languages (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
code VARCHAR(5) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)');
// Currencies
$this->addSql('CREATE TABLE currencies (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
code VARCHAR(3) NOT NULL UNIQUE,
symbol VARCHAR(10),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)');
}
public function down(Schema $schema): void
{
$this->addSql('DROP TABLE currencies');
$this->addSql('DROP TABLE languages');
$this->addSql('DROP TABLE countries');
}
}Schritt 3: Repository erstellen β
// src/Appi/Foundation/Geo/Repository/CountryRepositoryInterface.php
namespace App\Appi\Foundation\Geo\Repository;
use App\Appi\Foundation\Geo\Entity\Country;
interface CountryRepositoryInterface
{
public function save(Country $country): void;
public function findById(int $id): ?Country;
public function findAll(): array;
}
// src/Appi/Foundation/Geo/Repository/CountryRepository.php
namespace App\Appi\Foundation\Geo\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
class CountryRepository extends ServiceEntityRepository implements CountryRepositoryInterface
{
public function save(Country $country): void
{
$this->getEntityManager()->persist($country);
$this->getEntityManager()->flush();
}
// ... weitere Methoden
}Schritt 4: Admin-CRUD erstellen (im Dev Layer!) β
// β
CRUD Controller im Dev Layer
// src/Appi/Dev/Admin/Controller/CountryCrudController.php
namespace App\Appi\Dev\Admin\Controller;
use App\Appi\Foundation\Geo\Entity\Country; // β Import aus Foundation!
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
class CountryCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Country::class; // β Zeigt auf Foundation Entity!
}
public function configureFields(string $pageName): iterable
{
yield TextField::new('name', 'Name');
yield TextField::new('iso2', 'ISO 2');
yield TextField::new('iso3', 'ISO 3');
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('Country')
->setEntityLabelInPlural('Countries')
->setDefaultSort(['name' => 'ASC']);
}
}Schritt 5: Dashboard Integration β
// src/Appi/Dev/Admin/Controller/DashboardController.php
namespace App\Appi\Dev\Admin\Controller;
use App\Appi\Foundation\Geo\Entity\Country;
use App\Appi\Foundation\Geo\Entity\Language;
use App\Appi\Foundation\Geo\Entity\Currency;
use App\Appi\Core\Industry\Entity\Industry;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_ADMIN')] // Nur Users mit ROLE_ADMIN
class DashboardController extends AbstractDashboardController
{
#[Route('/admin', name: 'admin_dashboard')]
public function index(): Response
{
return $this->render('admin/dashboard.html.twig');
}
public function configureDashboard(): Dashboard
{
return Dashboard::new()
->setTitle('Appiyon Admin Panel');
}
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
// Geo-Daten (aus Foundation Layer)
yield MenuItem::section('Geo-Daten');
yield MenuItem::linkToCrud('Countries', 'fa fa-globe', Country::class);
yield MenuItem::linkToCrud('Languages', 'fa fa-language', Language::class);
yield MenuItem::linkToCrud('Currencies', 'fa fa-money-bill', Currency::class);
// Business-Daten (aus Core Layer)
yield MenuItem::section('Business-Daten');
yield MenuItem::linkToCrud('Industries', 'fa fa-industry', Industry::class);
// Weitere Sections...
}
}π Welche Daten gehΓΆren ins Admin Panel? β
β JA - Diese Daten SOLLTEN im Admin Panel sein: β
Globale Stammdaten (KEIN team_id):
- Countries, Languages, Currencies (Foundation/Geo)
- Industries, Categories, Tags (Core)
- Contract-Templates (Domain - wenn global)
- System-Settings
- Audit-Logs (Read-Only)
- System-Logs (Read-Only)
Kriterien:
- β Globale Daten (kein team_id)
- β MΓΌssen existieren BEVOR System nutzbar ist
- β Selten geΓ€ndert
- β Von Admins verwaltet
β NEIN - Diese Daten gehΓΆren NICHT ins Admin Panel: β
Mandanten-/Business-Daten (mit team_id):
- Teams (Identity-System - via App)
- Principals/Users (Identity-System - via App)
- CRM-EintrΓ€ge (Business-Daten - via App)
- Team-spezifische Items (Dev Layer - via App)
- Kundendaten (Domain - via App)
Kriterien:
- β Hat team_id
- β Business-spezifisch
- β Von normalen Users verwaltet
- β HΓ€ufig geΓ€ndert
π Security & Authentication β
Admin-Zugriff absichern β
// ALLE Admin-Controller absichern
#[IsGranted('ROLE_ADMIN')]
class DashboardController extends AbstractDashboardController
{
// Nur Users mit ROLE_ADMIN dΓΌrfen rein
}
#[IsGranted('ROLE_ADMIN')]
class CountryCrudController extends AbstractCrudController
{
// Auch alle CRUDs absichern
}Security Config β
# config/packages/security.yaml
security:
providers:
app_user_provider:
entity:
class: App\Appi\Shared\Identity\Entity\User
property: email
firewalls:
admin:
pattern: ^/admin
provider: app_user_provider
form_login:
login_path: admin_login
check_path: admin_login
logout:
path: admin_logout
target: admin_login
access_control:
- { path: ^/admin/login, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }Admins sind normale Users aus dem Identity-System!
π¨ Admin-Panel Template β
{# templates/admin/dashboard.html.twig #}
{% extends '@EasyAdmin/page/content.html.twig' %}
{% block content_title %}Appiyon Admin Panel{% endblock %}
{% block main %}
<div class="row">
<div class="col-md-3">
<div class="card">
<div class="card-body">
<h5>Countries</h5>
<p class="display-4">{{ stats.total_countries }}</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-body">
<h5>Languages</h5>
<p class="display-4">{{ stats.total_languages }}</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-body">
<h5>Industries</h5>
<p class="display-4">{{ stats.total_industries }}</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-body">
<h5>Total Users</h5>
<p class="display-4">{{ stats.total_users }}</p>
</div>
</div>
</div>
</div>
{% endblock %}π Checklist: Modul mit Admin-UI hinzufΓΌgen β
FΓΌr jedes Modul frage:
- Ist es global? (kein team_id) β JA? Weiter!
- Muss es vom Admin verwaltet werden? β JA? Weiter!
Dann:
- [ ] Entity erstellen im richtigen Layer (Foundation/Core/Domain)
- [ ] Repository erstellen (Interface + Implementation)
- [ ] Migration erstellen im richtigen Layer-Ordner
- [ ] Migration ausfΓΌhren + testen
- [ ] Admin-CRUD erstellen in
Dev/Admin/Controller/ - [ ] Dashboard aktualisieren (MenuItem hinzufΓΌgen)
- [ ] Security
#[IsGranted('ROLE_ADMIN')]prΓΌfen - [ ] Testen im Browser: https://appisym.go4family.net/admin
- [ ] Dokumentation aktualisieren
π« HΓ€ufige Fehler vermeiden β
β FALSCH - Entity im Dev Layer erstellen β
// β FALSCH!
// src/Appi/Dev/Admin/Entity/Country.php
namespace App\Appi\Dev\Admin\Entity;
class Country // NEIN! GehΓΆrt in Foundation!
{
// ...
}β RICHTIG - Entity im Foundation Layer β
// β
RICHTIG!
// src/Appi/Foundation/Geo/Entity/Country.php
namespace App\Appi\Foundation\Geo\Entity;
class Country // JA! Richtig!
{
// ...
}β FALSCH - Separate admins-Tabelle β
-- β FALSCH!
CREATE TABLE admins (
id SERIAL PRIMARY KEY,
email VARCHAR(255),
password VARCHAR(255)
);β RICHTIG - Users mit ROLE_ADMIN β
// β
RICHTIG!
// Admins sind Users aus Identity-System
$user = new User();
$user->setRoles(['ROLE_ADMIN']);π Beispiel: VollstΓ€ndige Modul-Struktur β
Geo-Modul (Countries, Languages, Currencies)
src/Appi/Foundation/Geo/
βββ Entity/
β βββ Country.php β Entity hier
β βββ Language.php
β βββ Currency.php
βββ Repository/
β βββ CountryRepositoryInterface.php
β βββ CountryRepository.php
β βββ LanguageRepositoryInterface.php
β βββ LanguageRepository.php
β βββ CurrencyRepositoryInterface.php
β βββ CurrencyRepository.php
βββ UseCase/
βββ (optional)
src/Appi/Dev/Admin/Controller/
βββ DashboardController.php β Admin Dashboard
βββ CountryCrudController.php β CRUD fΓΌr Country
βββ LanguageCrudController.php β CRUD fΓΌr Language
βββ CurrencyCrudController.php β CRUD fΓΌr Currency
migrations/3_foundation/
βββ Version0001_geo.php β Migration hier
templates/admin/
βββ dashboard.html.twig β Dashboard Templateπ Verwandte Dokumentation β
π Zusammenfassung β
Admin Panel ist:
- β Ein Web-UI (EasyAdmin)
- β Im Dev Layer (nur Controller)
- β Zeigt Entities aus allen Layern
- β FΓΌr Users mit ROLE_ADMIN
- β Verwaltet globale Stammdaten
Admin Panel ist NICHT:
- β Ein separates System
- β Mit eigenen Entities
- β Mit eigener admins-Tabelle
- β Im Infrastructure Layer
Workflow:
- Entity im richtigen Layer (Foundation/Core/Domain)
- Migration im richtigen Layer-Ordner
- CRUD-Controller im Dev/Admin/Controller
- Dashboard aktualisieren
- Mit ROLE_ADMIN absichern
Merke:
"Das Admin Panel ist nur das UI - die Entities bleiben in ihren Layern!"