feat: Add module removal command (app:module:remove)
Implementiert einen sicheren Weg zum Entfernen von Modulen aus dem System: Features: - Zeigt Modul-Informationen (Plugin + Datenbank) an - Widerruft Lizenz automatisch (optional mit --keep-license) - Löscht Datenbank-Eintrag im Permission-System (optional mit --keep-db-entry) - Warnt vor verknüpften Permissions - Zeigt nächste manuelle Schritte (Composer, Bundle, Migrations, Cache) - Bestätigung erforderlich (überspringbar mit --force) Dokumentation: - Command-Hilfe in src/Command/ModuleRemoveCommand.php - Benutzer-Anleitung in docs/PLUGIN_QUICKSTART.md - Entwickler-Referenz in CLAUDE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
77ce2c3043
commit
00b42e4a4c
18
CLAUDE.md
18
CLAUDE.md
@ -80,6 +80,24 @@ php bin/console cache:clear
|
|||||||
APP_ENV=prod php bin/console cache:warmup
|
APP_ENV=prod php bin/console cache:warmup
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Module Management (Plugin System)
|
||||||
|
```bash
|
||||||
|
# List all installed modules with license status
|
||||||
|
php bin/console app:module:list
|
||||||
|
|
||||||
|
# Register/manage module license
|
||||||
|
php bin/console app:module:license billing # Interactive
|
||||||
|
php bin/console app:module:license billing <license-key> # Direct
|
||||||
|
php bin/console app:module:license billing --validate # Validate
|
||||||
|
php bin/console app:module:license billing --revoke # Revoke
|
||||||
|
|
||||||
|
# Remove a module safely
|
||||||
|
php bin/console app:module:remove billing # Interactive
|
||||||
|
php bin/console app:module:remove billing --force # No confirmation
|
||||||
|
php bin/console app:module:remove billing --keep-license # Keep license
|
||||||
|
php bin/console app:module:remove billing --keep-db-entry # Keep DB entry
|
||||||
|
```
|
||||||
|
|
||||||
## Architecture Overview
|
## Architecture Overview
|
||||||
|
|
||||||
### Security System (6 Layers)
|
### Security System (6 Layers)
|
||||||
|
|||||||
82
composer.lock
generated
82
composer.lock
generated
@ -6578,16 +6578,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/monolog-bundle",
|
"name": "symfony/monolog-bundle",
|
||||||
"version": "v3.11.0",
|
"version": "v3.11.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/monolog-bundle.git",
|
"url": "https://github.com/symfony/monolog-bundle.git",
|
||||||
"reference": "e12eb92655b234cd50c21cda648088847a7ec777"
|
"reference": "0e675a6e08f791ef960dc9c7e392787111a3f0c1"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/e12eb92655b234cd50c21cda648088847a7ec777",
|
"url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/0e675a6e08f791ef960dc9c7e392787111a3f0c1",
|
||||||
"reference": "e12eb92655b234cd50c21cda648088847a7ec777",
|
"reference": "0e675a6e08f791ef960dc9c7e392787111a3f0c1",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -6634,7 +6634,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/symfony/monolog-bundle/issues",
|
"issues": "https://github.com/symfony/monolog-bundle/issues",
|
||||||
"source": "https://github.com/symfony/monolog-bundle/tree/v3.11.0"
|
"source": "https://github.com/symfony/monolog-bundle/tree/v3.11.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -6654,7 +6654,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-11-27T09:16:19+00:00"
|
"time": "2025-12-08T07:58:26+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/notifier",
|
"name": "symfony/notifier",
|
||||||
@ -9797,16 +9797,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "twig/extra-bundle",
|
"name": "twig/extra-bundle",
|
||||||
"version": "v3.22.1",
|
"version": "v3.22.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/twigphp/twig-extra-bundle.git",
|
"url": "https://github.com/twigphp/twig-extra-bundle.git",
|
||||||
"reference": "b6534bc925bec930004facca92fccebd0c809247"
|
"reference": "09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/b6534bc925bec930004facca92fccebd0c809247",
|
"url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e",
|
||||||
"reference": "b6534bc925bec930004facca92fccebd0c809247",
|
"reference": "09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -9855,7 +9855,7 @@
|
|||||||
"twig"
|
"twig"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.22.1"
|
"source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.22.2"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -9867,7 +9867,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-11-02T11:00:49+00:00"
|
"time": "2025-12-05T08:51:53+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "twig/twig",
|
"name": "twig/twig",
|
||||||
@ -10362,16 +10362,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nikic/php-parser",
|
"name": "nikic/php-parser",
|
||||||
"version": "v5.6.2",
|
"version": "v5.7.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||||
"reference": "3a454ca033b9e06b63282ce19562e892747449bb"
|
"reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb",
|
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
|
||||||
"reference": "3a454ca033b9e06b63282ce19562e892747449bb",
|
"reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -10414,9 +10414,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2"
|
"source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
|
||||||
},
|
},
|
||||||
"time": "2025-10-21T19:32:17+00:00"
|
"time": "2025-12-06T11:56:16+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phar-io/manifest",
|
"name": "phar-io/manifest",
|
||||||
@ -10538,23 +10538,23 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
"version": "12.5.0",
|
"version": "12.5.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||||
"reference": "bca180c050dd3ae15f87c26d25cabb34fe1a0a5a"
|
"reference": "c467c59a4f6e04b942be422844e7a6352fa01b57"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bca180c050dd3ae15f87c26d25cabb34fe1a0a5a",
|
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c467c59a4f6e04b942be422844e7a6352fa01b57",
|
||||||
"reference": "bca180c050dd3ae15f87c26d25cabb34fe1a0a5a",
|
"reference": "c467c59a4f6e04b942be422844e7a6352fa01b57",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"ext-libxml": "*",
|
"ext-libxml": "*",
|
||||||
"ext-xmlwriter": "*",
|
"ext-xmlwriter": "*",
|
||||||
"nikic/php-parser": "^5.6.2",
|
"nikic/php-parser": "^5.7.0",
|
||||||
"php": ">=8.3",
|
"php": ">=8.3",
|
||||||
"phpunit/php-file-iterator": "^6.0",
|
"phpunit/php-file-iterator": "^6.0",
|
||||||
"phpunit/php-text-template": "^5.0",
|
"phpunit/php-text-template": "^5.0",
|
||||||
@ -10562,10 +10562,10 @@
|
|||||||
"sebastian/environment": "^8.0.3",
|
"sebastian/environment": "^8.0.3",
|
||||||
"sebastian/lines-of-code": "^4.0",
|
"sebastian/lines-of-code": "^4.0",
|
||||||
"sebastian/version": "^6.0",
|
"sebastian/version": "^6.0",
|
||||||
"theseer/tokenizer": "^1.3.1"
|
"theseer/tokenizer": "^2.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^12.4.4"
|
"phpunit/phpunit": "^12.5.1"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-pcov": "PHP extension that provides line coverage",
|
"ext-pcov": "PHP extension that provides line coverage",
|
||||||
@ -10603,7 +10603,7 @@
|
|||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||||
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
||||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.0"
|
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -10623,7 +10623,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-11-29T07:15:54+00:00"
|
"time": "2025-12-08T07:17:58+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-file-iterator",
|
"name": "phpunit/php-file-iterator",
|
||||||
@ -10872,16 +10872,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/phpunit",
|
"name": "phpunit/phpunit",
|
||||||
"version": "12.5.0",
|
"version": "12.5.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||||
"reference": "fef037fe50d20ce826cdbd741b7a2afcdec5f45b"
|
"reference": "6dc2e076d09960efbb0c1272aa9bc156fc80955e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fef037fe50d20ce826cdbd741b7a2afcdec5f45b",
|
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6dc2e076d09960efbb0c1272aa9bc156fc80955e",
|
||||||
"reference": "fef037fe50d20ce826cdbd741b7a2afcdec5f45b",
|
"reference": "6dc2e076d09960efbb0c1272aa9bc156fc80955e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -10895,7 +10895,7 @@
|
|||||||
"phar-io/manifest": "^2.0.4",
|
"phar-io/manifest": "^2.0.4",
|
||||||
"phar-io/version": "^3.2.1",
|
"phar-io/version": "^3.2.1",
|
||||||
"php": ">=8.3",
|
"php": ">=8.3",
|
||||||
"phpunit/php-code-coverage": "^12.5.0",
|
"phpunit/php-code-coverage": "^12.5.1",
|
||||||
"phpunit/php-file-iterator": "^6.0.0",
|
"phpunit/php-file-iterator": "^6.0.0",
|
||||||
"phpunit/php-invoker": "^6.0.0",
|
"phpunit/php-invoker": "^6.0.0",
|
||||||
"phpunit/php-text-template": "^5.0.0",
|
"phpunit/php-text-template": "^5.0.0",
|
||||||
@ -10949,7 +10949,7 @@
|
|||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.0"
|
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -10973,7 +10973,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-12-05T04:59:40+00:00"
|
"time": "2025-12-11T08:52:59+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/cli-parser",
|
"name": "sebastian/cli-parser",
|
||||||
@ -12379,23 +12379,23 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "theseer/tokenizer",
|
"name": "theseer/tokenizer",
|
||||||
"version": "1.3.1",
|
"version": "2.0.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/theseer/tokenizer.git",
|
"url": "https://github.com/theseer/tokenizer.git",
|
||||||
"reference": "b7489ce515e168639d17feec34b8847c326b0b3c"
|
"reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c",
|
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4",
|
||||||
"reference": "b7489ce515e168639d17feec34b8847c326b0b3c",
|
"reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"ext-tokenizer": "*",
|
"ext-tokenizer": "*",
|
||||||
"ext-xmlwriter": "*",
|
"ext-xmlwriter": "*",
|
||||||
"php": "^7.2 || ^8.0"
|
"php": "^8.1"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@ -12417,7 +12417,7 @@
|
|||||||
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
|
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/theseer/tokenizer/issues",
|
"issues": "https://github.com/theseer/tokenizer/issues",
|
||||||
"source": "https://github.com/theseer/tokenizer/tree/1.3.1"
|
"source": "https://github.com/theseer/tokenizer/tree/2.0.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -12425,7 +12425,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-11-17T20:03:58+00:00"
|
"time": "2025-12-08T11:19:18+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
|
|||||||
@ -223,6 +223,132 @@ Navigiere zu: `http://localhost:8000/admin/modules`
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🗑️ Modul entfernen
|
||||||
|
|
||||||
|
### Option A: CLI (empfohlen)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Interaktiv mit Bestätigung
|
||||||
|
php bin/console app:module:remove billing
|
||||||
|
|
||||||
|
# Ohne Bestätigung (force)
|
||||||
|
php bin/console app:module:remove billing --force
|
||||||
|
|
||||||
|
# Lizenz behalten
|
||||||
|
php bin/console app:module:remove billing --keep-license
|
||||||
|
|
||||||
|
# Permission-Datenbank-Eintrag behalten
|
||||||
|
php bin/console app:module:remove billing --keep-db-entry
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Command führt folgende Schritte durch:
|
||||||
|
|
||||||
|
1. ✓ **Modul-Informationen anzeigen** (Plugin + Datenbank)
|
||||||
|
2. ✓ **Bestätigung einholen** (außer mit `--force`)
|
||||||
|
3. ✓ **Lizenz widerrufen** (außer mit `--keep-license`)
|
||||||
|
4. ✓ **Datenbank-Eintrag löschen** (außer mit `--keep-db-entry`)
|
||||||
|
5. → **Composer-Command vorschlagen**
|
||||||
|
|
||||||
|
### Manuelle Schritte nach app:module:remove
|
||||||
|
|
||||||
|
Der Command zeigt dir die nötigen manuellen Schritte:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Composer-Package entfernen
|
||||||
|
composer remove mycrm/billing-module
|
||||||
|
|
||||||
|
# 2. Bundle aus config/bundles.php entfernen (falls vorhanden)
|
||||||
|
# Öffne config/bundles.php und entferne die Zeile:
|
||||||
|
# MyCRM\BillingModule\BillingBundle::class => ['all' => true],
|
||||||
|
|
||||||
|
# 3. Optional: Migrationen zurückrollen (falls benötigt)
|
||||||
|
php bin/console doctrine:migrations:migrate prev
|
||||||
|
|
||||||
|
# 4. Cache leeren
|
||||||
|
php bin/console cache:clear
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option B: Vollständig manuell
|
||||||
|
|
||||||
|
Falls du den Command nicht nutzen möchtest:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Lizenz widerrufen
|
||||||
|
php bin/console app:module:license billing --revoke
|
||||||
|
|
||||||
|
# 2. Composer-Package entfernen
|
||||||
|
composer remove mycrm/billing-module
|
||||||
|
|
||||||
|
# 3. Bundle aus config/bundles.php entfernen (manuell editieren)
|
||||||
|
|
||||||
|
# 4. Optional: Module-Entity aus DB löschen
|
||||||
|
# Nur nötig, wenn das Modul auch im Permission-System registriert ist
|
||||||
|
# VORSICHT: Löscht auch verknüpfte RolePermissions!
|
||||||
|
|
||||||
|
# 5. Cache leeren
|
||||||
|
php bin/console cache:clear
|
||||||
|
```
|
||||||
|
|
||||||
|
### Beispiel-Ausgabe
|
||||||
|
|
||||||
|
```
|
||||||
|
Modul "billing" entfernen
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Modul-Informationen
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Eigenschaft Wert
|
||||||
|
Plugin-Informationen
|
||||||
|
Name Rechnungsmodul
|
||||||
|
Version 1.0.0
|
||||||
|
Beschreibung Rechnungsverwaltung für myCRM
|
||||||
|
Lizenziert ✓ Ja
|
||||||
|
Aktiv ✓ Ja
|
||||||
|
|
||||||
|
Datenbank-Informationen (Permission-System)
|
||||||
|
Name Rechnungen
|
||||||
|
Code billing
|
||||||
|
Aktiv ✓ Ja
|
||||||
|
Permissions 3
|
||||||
|
|
||||||
|
! [NOTE] Möchten Sie dieses Modul wirklich entfernen? (yes/no) [no]:
|
||||||
|
> yes
|
||||||
|
|
||||||
|
Entferne Modul
|
||||||
|
--------------
|
||||||
|
|
||||||
|
⏳ Widerrufe Lizenz...
|
||||||
|
[OK] ✓ Lizenz erfolgreich widerrufen
|
||||||
|
|
||||||
|
⏳ Entferne Datenbank-Eintrag (Permission-System)...
|
||||||
|
! [WARNING] ACHTUNG: Dieses Modul hat 3 verknüpfte Permission(s). Diese werden ebenfalls gelöscht!
|
||||||
|
! [NOTE] Trotzdem fortfahren? (yes/no) [no]:
|
||||||
|
> yes
|
||||||
|
[OK] ✓ Datenbank-Eintrag erfolgreich entfernt
|
||||||
|
|
||||||
|
Zusammenfassung
|
||||||
|
---------------
|
||||||
|
|
||||||
|
* ✓ Lizenz widerrufen
|
||||||
|
* ✓ Datenbank-Eintrag gelöscht
|
||||||
|
|
||||||
|
Nächste Schritte (manuell)
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
* 1. Composer-Package entfernen: composer remove mycrm/billing-module
|
||||||
|
* 2. Bundle aus config/bundles.php entfernen (falls vorhanden)
|
||||||
|
* 3. Optional: Migrationen zurückrollen (falls benötigt)
|
||||||
|
php bin/console doctrine:migrations:migrate prev
|
||||||
|
* 4. Cache leeren: php bin/console cache:clear
|
||||||
|
|
||||||
|
[OK] Modul "billing" wurde vorbereitet zum Entfernen. Führen Sie die obigen Schritte manuell aus.
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Modul sicher entfernt!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🔧 Troubleshooting
|
## 🔧 Troubleshooting
|
||||||
|
|
||||||
### Problem: "Modul wird nicht erkannt"
|
### Problem: "Modul wird nicht erkannt"
|
||||||
|
|||||||
203
src/Command/ModuleRemoveCommand.php
Normal file
203
src/Command/ModuleRemoveCommand.php
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\Entity\Module;
|
||||||
|
use App\Plugin\LicenseValidatorInterface;
|
||||||
|
use App\Plugin\ModuleRegistry;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLI-Command zum sicheren Entfernen von Modulen
|
||||||
|
*/
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:module:remove',
|
||||||
|
description: 'Entfernt ein Modul sicher aus dem System',
|
||||||
|
)]
|
||||||
|
class ModuleRemoveCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ModuleRegistry $moduleRegistry,
|
||||||
|
private readonly LicenseValidatorInterface $licenseValidator,
|
||||||
|
private readonly EntityManagerInterface $entityManager
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addArgument('module', InputArgument::REQUIRED, 'Modul-Identifier (z.B. "billing")')
|
||||||
|
->addOption('keep-license', null, InputOption::VALUE_NONE, 'Lizenz behalten (nicht widerrufen)')
|
||||||
|
->addOption('keep-db-entry', null, InputOption::VALUE_NONE, 'Datenbank-Eintrag behalten (Permission-System)')
|
||||||
|
->addOption('force', 'f', InputOption::VALUE_NONE, 'Keine Bestätigung erforderlich')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$moduleIdentifier = $input->getArgument('module');
|
||||||
|
$keepLicense = $input->getOption('keep-license');
|
||||||
|
$keepDbEntry = $input->getOption('keep-db-entry');
|
||||||
|
$force = $input->getOption('force');
|
||||||
|
|
||||||
|
$io->title(sprintf('Modul "%s" entfernen', $moduleIdentifier));
|
||||||
|
|
||||||
|
// 1. Modul-Informationen sammeln
|
||||||
|
$modulePlugin = $this->moduleRegistry->getModule($moduleIdentifier);
|
||||||
|
$moduleEntity = $this->findModuleEntity($moduleIdentifier);
|
||||||
|
|
||||||
|
if (!$modulePlugin && !$moduleEntity) {
|
||||||
|
$io->error(sprintf('Modul "%s" wurde nicht gefunden (weder als Plugin noch in der Datenbank).', $moduleIdentifier));
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Informationen anzeigen
|
||||||
|
$this->displayModuleInfo($io, $modulePlugin, $moduleEntity);
|
||||||
|
|
||||||
|
// 3. Bestätigung einholen
|
||||||
|
if (!$force && !$io->confirm('Möchten Sie dieses Modul wirklich entfernen?', false)) {
|
||||||
|
$io->info('Abgebrochen.');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->section('Entferne Modul');
|
||||||
|
|
||||||
|
$steps = [];
|
||||||
|
|
||||||
|
// 4. Lizenz widerrufen
|
||||||
|
if (!$keepLicense && $modulePlugin && $modulePlugin->isLicensed()) {
|
||||||
|
$io->text('⏳ Widerrufe Lizenz...');
|
||||||
|
try {
|
||||||
|
$this->licenseValidator->revokeLicense($moduleIdentifier);
|
||||||
|
$io->success('✓ Lizenz erfolgreich widerrufen');
|
||||||
|
$steps[] = '✓ Lizenz widerrufen';
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$io->warning(sprintf('Lizenz konnte nicht widerrufen werden: %s', $e->getMessage()));
|
||||||
|
$steps[] = '⚠ Lizenz-Widerruf fehlgeschlagen';
|
||||||
|
}
|
||||||
|
} elseif ($keepLicense) {
|
||||||
|
$io->text('⏭ Lizenz wird behalten (--keep-license)');
|
||||||
|
$steps[] = '⏭ Lizenz behalten';
|
||||||
|
} else {
|
||||||
|
$io->text('⏭ Keine Lizenz vorhanden');
|
||||||
|
$steps[] = '⏭ Keine Lizenz';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Datenbank-Eintrag entfernen
|
||||||
|
if (!$keepDbEntry && $moduleEntity) {
|
||||||
|
$io->text('⏳ Entferne Datenbank-Eintrag (Permission-System)...');
|
||||||
|
|
||||||
|
// Warnung, wenn Permissions existieren
|
||||||
|
$permissionCount = $moduleEntity->getPermissions()->count();
|
||||||
|
if ($permissionCount > 0) {
|
||||||
|
$io->warning(sprintf(
|
||||||
|
'ACHTUNG: Dieses Modul hat %d verknüpfte Permission(s). Diese werden ebenfalls gelöscht!',
|
||||||
|
$permissionCount
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!$force && !$io->confirm('Trotzdem fortfahren?', false)) {
|
||||||
|
$io->info('Abgebrochen.');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->entityManager->remove($moduleEntity);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
$io->success('✓ Datenbank-Eintrag erfolgreich entfernt');
|
||||||
|
$steps[] = '✓ Datenbank-Eintrag gelöscht';
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$io->error(sprintf('Datenbank-Eintrag konnte nicht entfernt werden: %s', $e->getMessage()));
|
||||||
|
$steps[] = '✗ Datenbank-Eintrag Fehler';
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
} elseif ($keepDbEntry) {
|
||||||
|
$io->text('⏭ Datenbank-Eintrag wird behalten (--keep-db-entry)');
|
||||||
|
$steps[] = '⏭ DB-Eintrag behalten';
|
||||||
|
} else {
|
||||||
|
$io->text('⏭ Kein Datenbank-Eintrag vorhanden');
|
||||||
|
$steps[] = '⏭ Kein DB-Eintrag';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Nächste Schritte anzeigen
|
||||||
|
$io->section('Zusammenfassung');
|
||||||
|
$io->listing($steps);
|
||||||
|
|
||||||
|
$io->section('Nächste Schritte (manuell)');
|
||||||
|
|
||||||
|
$nextSteps = [];
|
||||||
|
|
||||||
|
// Composer-Package entfernen
|
||||||
|
if ($modulePlugin) {
|
||||||
|
$packageName = $this->guessPackageName($moduleIdentifier);
|
||||||
|
$nextSteps[] = sprintf('1. Composer-Package entfernen: <comment>composer remove %s</comment>', $packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bundle aus config/bundles.php entfernen
|
||||||
|
$nextSteps[] = '2. Bundle aus <comment>config/bundles.php</comment> entfernen (falls vorhanden)';
|
||||||
|
|
||||||
|
// Migrationen
|
||||||
|
$nextSteps[] = '3. Optional: Migrationen zurückrollen (falls benötigt)';
|
||||||
|
$nextSteps[] = ' <comment>php bin/console doctrine:migrations:migrate prev</comment>';
|
||||||
|
|
||||||
|
// Cache leeren
|
||||||
|
$nextSteps[] = '4. Cache leeren: <comment>php bin/console cache:clear</comment>';
|
||||||
|
|
||||||
|
$io->listing($nextSteps);
|
||||||
|
|
||||||
|
$io->success(sprintf('Modul "%s" wurde vorbereitet zum Entfernen. Führen Sie die obigen Schritte manuell aus.', $moduleIdentifier));
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function displayModuleInfo(SymfonyStyle $io, $modulePlugin, ?Module $moduleEntity): void
|
||||||
|
{
|
||||||
|
$io->section('Modul-Informationen');
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
|
||||||
|
if ($modulePlugin) {
|
||||||
|
$rows[] = ['<info>Plugin-Informationen</info>', ''];
|
||||||
|
$rows[] = [' Name', $modulePlugin->getDisplayName()];
|
||||||
|
$rows[] = [' Version', $modulePlugin->getVersion()];
|
||||||
|
$rows[] = [' Beschreibung', $modulePlugin->getDescription()];
|
||||||
|
$rows[] = [' Lizenziert', $modulePlugin->isLicensed() ? '✓ Ja' : '✗ Nein'];
|
||||||
|
$rows[] = [' Aktiv', $this->moduleRegistry->isModuleActive($modulePlugin->getIdentifier()) ? '✓ Ja' : '✗ Nein'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($moduleEntity) {
|
||||||
|
if (!empty($rows)) {
|
||||||
|
$rows[] = ['', ''];
|
||||||
|
}
|
||||||
|
$rows[] = ['<info>Datenbank-Informationen (Permission-System)</info>', ''];
|
||||||
|
$rows[] = [' Name', $moduleEntity->getName()];
|
||||||
|
$rows[] = [' Code', $moduleEntity->getCode()];
|
||||||
|
$rows[] = [' Aktiv', $moduleEntity->isActive() ? '✓ Ja' : '✗ Nein'];
|
||||||
|
$rows[] = [' Permissions', (string)$moduleEntity->getPermissions()->count()];
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->table(['Eigenschaft', 'Wert'], $rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findModuleEntity(string $identifier): ?Module
|
||||||
|
{
|
||||||
|
return $this->entityManager
|
||||||
|
->getRepository(Module::class)
|
||||||
|
->findOneBy(['code' => $identifier]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function guessPackageName(string $identifier): string
|
||||||
|
{
|
||||||
|
// Versuche Package-Name zu erraten (z.B. billing -> mycrm/billing-module)
|
||||||
|
return sprintf('mycrm/%s-module', $identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user