diff --git a/docs/_static/css/extra.css b/docs/_static/css/extra.css index 0f9bb90..6125dd2 100644 --- a/docs/_static/css/extra.css +++ b/docs/_static/css/extra.css @@ -1,3 +1,15 @@ .wy-menu p.caption { - margin-top: 10px; + margin-top: 20px; +} + +table { + margin-bottom: 15px; +} + +td, th { + padding: 5px 10px; +} + +td code { + border: 0 !important; } diff --git a/docs/crud/configuration.md b/docs/crud/configuration.md index 94c18d7..6b82dad 100644 --- a/docs/crud/configuration.md +++ b/docs/crud/configuration.md @@ -1,2 +1,241 @@ # Configuration +A generated crud controller contains a method named `getConfiguration`. This methods returns a instance of `App\Core\Crud\CrudConfiguration`. + +## setPageTitle + +`setPageTitle(string $page, string $title)` + +Set the title of the given page. + +Example of usage in a CRUD template: `{{ configuration.pageTitle('index') }}`. + +## setPageRoute + +`setPageRoute(string $page, string $route)` + +Set the route of the given page. By default, pages are: `index`, `edit`, `new`, `show`. You can create a custom page for a custom controller. + +Example of usage in a CRUD template: `...`. + +## setForm + +`setForm(string $context, string $form, `array` $options = [])` + +Set the form used in the given context. + +## setFormOptions + +`setFormOptions(string $context, `array` $options = [])` + +Defines options given to a form. + +## setAction + +`setAction(string $page, string $action, bool $enabled)` + +Set if an action is enabled or not in the given page. Take a look at `core/Resources/views/admin/crud/*.html.twig` for more information. + +Usage in a CRUD template: `{% if configuration.action('index', 'new')%}...{% endif %}`. + +## setActionTitle + +`setActionTitle(string $page, string $action, string $title)` + +Set the title of an action in the given page. + +Example of usage in a CRUD template: `{{ configuration.actionTitle(context, 'new', 'New')|trans }}` + +## setView + +`setView(string $context, string $view)` + +Override a view. + +| Controller (context) | View | Description | +| ---------- | ---- | ----------- | +| `index` | `@Core/admin/crud/index.html.twig` | Template of the page `index` | +| `edit` | `@Core/admin/crud/edit.html.twig` | Template of the page `edit` | +| `new` | `@Core/admin/crud/new.html.twig` | Template of the page `new` | +| `show` | `@Core/admin/crud/show.html.twig` | Template of the page `show` | +| `filter` | `@Core/admin/crud/filter.html.twig` | Template of the page `filter` | + +| Form (context) | View | Description | +| ---- | ---- | ----------- | +| `form` | `@Core/admin/crud/_form.html.twig` | Template to render a form | +| `form_widget` | `@Core/admin/crud/_form_widget.html.twig` | Template to render a form widget | +| `form_translations` | `@Core/admin/crud/_form_translations.html.twig` | Template to render a the translation field | + +| Entity (context) | View | Description | +| ------ | ---- | ----------- | +| `show_entity` | @Core/admin/crud/_show.html.twig | Template to render the entity | + +## setViewDatas + +`setViewDatas(string $context, `array` $datas)` and `addViewData(string $context, string $name, $value)` + +Add datas given to a view. Useful in a custom controller. + +## setField + +`setField(string $context, string $label, string $field, `array` $options)` + +Add a field displayed in the given context. Used in the index. + +``` +use App\Core\Crud\Field; + +$configuration->setField('index', 'Title', Field\TextField::class, [ + // options +]) +``` + +All fields have these options: + +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| `property` | `string` | `null` | Entity's property to display | +| `property__builder` | `callable` | `null` | A callable data and used to generate the content displayed | +| `view` | `string` | `@Core/admin/crud/field/text.html.twig` | The templated rendered | +| `raw` | `boolean` | `false` | Render as HTML | +| `sort` | `array|callable` | `null` | Defines how to sort | + +Examples: + +``` +$configuration->setField('index', 'My field', TextField::class, [ + 'property' => 'myProperty', + // OR + 'property_builder' => function($entity, array $options) { + return $entity->getMyProperty(); + }, +]) + +$configuration->setField('index', 'My field', TextField::class, [ + 'raw' => true, + 'property_builder' => function($entity, array $options) { + return sprintf('%s', $entity->getBar()); + }, +]) + +// https://127.0.0.7:8000/admin/my_entity?_sort=property&_sort_direction=asc +$configuration->setField('index', 'My field', TextField::class, [ + 'property' => 'myProperty' + 'sort' => ['property', '.myProperty'], + // OR + 'sort' => ['property', function(MyEntityRepositoryQuery $query, $direction) { + $query->orderBy('.myProperty', $direction); + }], +]) +``` + +### TextField + +`App\Core\Crud\Field\TextField` + +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| `view` | `string` | `@Core/admin/crud/field/text.html.twig` | The templated rendered | + +### DateField + +`App\Core\Crud\Field\DateField` + +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| `view` | `string` | `@Core/admin/crud/field/date.html.twig` | The templated rendered | +| `format` | `string` | `Y-m-d` | The date format | + +### DatetimeField + +`App\Core\Crud\Field\DatetimeField` + +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| `view` | `string` | `@Core/admin/crud/field/date.html.twig` | The templated rendered | +| `format` | `string` | `Y-m-d H:i:s` | The date format | + +### ButtonField + +`App\Core\Crud\Field\ButtonField` + +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| `view` | `string` | `@Core/admin/crud/field/button.html.twig` | The templated rendered | +| `button_attr` | `array` | `[]` | Button HTML attributes | +| `button_tag` | `string` | `button` | HTML tag of the button | + +### ImageField + +`App\Core\Crud\Field\ImageField` + +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| `view` | `string` | `@Core/admin/crud/field/image.html.twig` | The templated rendered | +| `image_attr` | `array` | `[]` | Image HTML attributes | + +## setMaxPerPage + +`setMaxPerPage(string $page, int $max)` + +Set how many elements are displayed in a single page. + +## setI18n + +`setI18n(array $locales, string $defaultLocale)` + +Set an array of locales for a translatable entity. The default locale is used in the index page. +Compatible with [https://github.com/KnpLabs/DoctrineBehaviors/blob/master/docs/translatable.md](https://github.com/KnpLabs/DoctrineBehaviors/blob/master/docs/translatable.md). + +## setDefaultSort + +`setDefaultSort(string $context, string $label, string $direction = 'asc')` + +Set the default sort applied in the repository query. + +``` +$configuration + ->setDefaultSort('index', 'title', 'asc') + ->setField('index', 'Title', Field\TextField::class, [ + 'property' => 'title', + 'sort' => ['title', '.title'], + ]); +``` + +## setIsSortableCollection + +`setIsSortableCollection(string $page, bool $isSortableCollection)` + +It enables the drag & drop to sort entities. + +``` +class MyEntity implements EntityInterface +{ + // ... + + /** + * @ORM\Column(type="integer", nullable=true) + */ + private $sortOrder; + + public function getSortOrder(): ?int + { + return $this->sortOrder; + } + + public function setSortOrder(?int $sortOrder): self + { + $this->sortOrder = $sortOrder; + + return $this; + } + + // ... +} +``` + +## setSortableCollectionProperty + +`setSortableCollectionProperty(string $sortableCollectionProperty)` + +In order to sort entities, the default property used is `sortOrder`. You can set something else. diff --git a/docs/crud/generator.md b/docs/crud/generator.md index fd7bc68..de7db25 100644 --- a/docs/crud/generator.md +++ b/docs/crud/generator.md @@ -1,2 +1,145 @@ # Generator +## Prepare your entity + +* implements `App\Core\Entity\EntityInterface` (see [Entity Manager](/entities/em/)) +* creates a repository query (see [Repository Query](/entities/query/)) +* creates a factory (see [Factory](/entities/factory/)) +* generates a form (see `php bin/console make:form --help`) + +## Generation + +The generation is performed in CLI. These information are required: + +* The name of the futur controller (eg: `MyEntityAdminController`) +* The namespace of the entity (eg: `App\Entity\MyEntity`) +* The namespace of the entity repository query (eg: `App\Repository\MyEntityRepositoryQuery`) +* The namespace of the the entity factory (eg: `App\Factory\MyEntityFactory`) +* The namespace of the form used to create and update the entity (eg: `App\Form\MyEntityType`) + +Simply run `php bin/console make:crud-controller`. + +The next controller will be generated: + +``` +// src/Controller/MyEntityAdminController +namespace App\Controller; + +use App\Core\Controller\Admin\Crud\CrudController; +use App\Core\Crud\CrudConfiguration; +use App\Core\Crud\Field; +use App\Core\Entity\EntityInterface; +use App\Core\Manager\EntityManager; +use App\Entity\MyEntity as Entity; +use App\Factory\MyEntityFactory as Factory; +use App\Form\MyEntityType as Type; +use App\Repository\MyEntityRepositoryQuery as RepositoryQuery; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\Routing\Annotation\Route; + +class MyEntityAdminController extends CrudController +{ + /** + * @Route("/admin/my_entity/{page}", name="admin_my_entity_index", methods={"GET"}, requirements={"page":"\d+"}) + */ + public function index(int $page = 1, RepositoryQuery $query, Request $request, Session $session): Response + { + return $this->doIndex($page, $query, $request, $session); + } + + /** + * @Route("/admin/my_entity/new", name="admin_my_entity_new", methods={"GET", "POST"}) + */ + public function new(Factory $factory, EntityManager $entityManager, Request $request): Response + { + return $this->doNew($factory->create(), $entityManager, $request); + } + + /** + * @Route("/admin/my_entity/show/{entity}", name="admin_my_entity_show", methods={"GET"}) + */ + public function show(Entity $entity): Response + { + return $this->doShow($entity); + } + + /** + * @Route("/admin/my_entity/filter", name="admin_my_entity_filter", methods={"GET"}) + */ + public function filter(Session $session): Response + { + return $this->doFilter($session); + } + + /** + * @Route("/admin/my_entity/edit/{entity}", name="admin_my_entity_edit", methods={"GET", "POST"}) + */ + public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response + { + return $this->doEdit($entity, $entityManager, $request); + } + + /** + * @Route("/admin/my_entity/sort/{page}", name="admin_my_entity_sort", methods={"POST"}, requirements={"page":"\d+"}) + */ + public function sort(int $page = 1, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response + { + return $this->doSort($page, $query, $entityManager, $request, $session); + } + + /** + * @Route("/admin/my_entity/delete/{entity}", name="admin_my_entity_delete", methods={"DELETE"}) + */ + public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response + { + return $this->doDelete($entity, $entityManager, $request); + } + + protected function getConfiguration(): CrudConfiguration + { + return CrudConfiguration::create() + ->setPageTitle('index', 'List of App\Entity\MyEntity') + ->setPageTitle('edit', 'Edition of {id}') + ->setPageTitle('new', 'New App\Entity\MyEntity') + ->setPageTitle('show', 'View of {id}') + + ->setPageRoute('index', 'admin_my_entity_index') + ->setPageRoute('new', 'admin_my_entity_new') + ->setPageRoute('edit', 'admin_my_entity_edit') + ->setPageRoute('show', 'admin_my_entity_show') + ->setPageRoute('sort', 'admin_my_entity_sort') + ->setPageRoute('delete', 'admin_my_entity_delete') + ->setPageRoute('filter', 'admin_my_entity_filter') + + ->setForm('edit', Type::class, []) + ->setForm('new', Type::class) + // ->setForm('filter', Type::class) + + // ->setMaxPerPage('index', 20) + + // ->setIsSortableCollection('index', false) + // ->setSortableCollectionProperty('sortOrder') + + // ->setAction('index', 'new', true) + // ->setAction('index', 'show', true) + // ->setAction('index', 'edit', true) + // ->setAction('index', 'delete', true) + + // ->setAction('edit', 'back', true) + // ->setAction('edit', 'show', true) + // ->setAction('edit', 'delete', true) + + // ->setField('index', 'Label', Field\TextField::class, [ + // 'property' => 'label', + // ]) + ; + } + + protected function getSection(): string + { + return 'my_entity'; + } +} +``` diff --git a/docs/crud/index.md b/docs/crud/index.md index 7baeaf3..dd5c171 100644 --- a/docs/crud/index.md +++ b/docs/crud/index.md @@ -1,2 +1,19 @@ # CRUD +Murph helps you to manage specific entities with a CRUD manager: + +* **C**reate entities +* **R**ead (Show) entities +* **U**pdate entities +* **D**elete entities + +You can configure almost anything: + +* How to list entities: + * Information to show + * Available actions + * Filters + * Sorting + * ... +* How to show an entity +* How to create and update an entity diff --git a/docs/entities/em.md b/docs/entities/em.md new file mode 100644 index 0000000..f3e235b --- /dev/null +++ b/docs/entities/em.md @@ -0,0 +1,135 @@ +# Entity Manager + +The entity manager of Muprh is a proxy of the Doctrine's entity manager. It gives you an easy way to create, update and delete an entity and dispatches events easy to subscribe to. + +## Implementation + +Entities must implements `App\Core\Entity\EntityInterface`. + +``` +// src/Entity/MyEntity.php +namespace App\Entity; + +use App\Repository\MyEntityRepository; +use Doctrine\ORM\Mapping as ORM; +use App\Core\Entity\EntityInterface; + +/** + * @ORM\Entity(repositoryClass=MyEntityRepository::class) + */ +class MyEntity implements EntityInterface +{ + // ... +} +``` + +## Usage + +There are 2 entity managers which are services: + +* `App\Core\Manager\EntityManager` used for all entities +* `App\Core\Manager\TranslatableEntityManager` used for translatable entities + +``` +// src/Controller/FooController.php +namespace App\Controller; + +use App\Core\Manager\EntityManager +use App\Entity\MyEntity; +use Symfony\Component\HttpFoundation\Response; + +class FooController +{ + public function foo(EntityManager $entityManager): Response + { + $myEntity = new MyEntity(); + + // Creates an entity + $entityManager->create($myEntity); + + // Updates an entity + $entityManager->update($myEntity); + + // Deletes an entity + $entityManager->delete($myEntity); + + // ... + } +} +``` + +## Events + +Events are dispatched before and after creation, update and deletion. All entities of Muprh use the entity manager. + +``` +// src/EventSuscriber/MyEntityEventSubscriber.php +namespace App\EventSuscriber; + +use App\Core\Entity\EntityInterface; +use App\Core\Event\EntityManager\EntityManagerEvent; +use App\Core\EventSuscriber\EntityManagerEventSubscriber; +use App\Entity\MyEntity; + +class MyEntityEventSubscriber extends EntityManagerEventSubscriber +{ + public function support(EntityInterface $entity) + { + return $entity instanceof MyEntity; + } + + public function onCreate(EntityManagerEvent $event) + { + if (!$this->support($event->getEntity())) { + return; + } + + // ... + } + + public function onUpdate(EntityManagerEvent $event) + { + if (!$this->support($event->getEntity())) { + return; + } + + // ... + } + + public function onDelete(EntityManagerEvent $event) + { + if (!$this->support($event->getEntity())) { + return; + } + + // ... + } + + public function onPreCreate(EntityManagerEvent $event) + { + if (!$this->support($event->getEntity())) { + return; + } + + // ... + } + + public function onPreUpdate(EntityManagerEvent $event) + { + if (!$this->support($event->getEntity())) { + return; + } + + // ... + } + + public function onPreDelete(EntityManagerEvent $event) + { + if (!$this->support($event->getEntity())) { + return; + } + + // ... + } +} +``` diff --git a/docs/entities/factory.md b/docs/entities/factory.md new file mode 100644 index 0000000..61df690 --- /dev/null +++ b/docs/entities/factory.md @@ -0,0 +1,44 @@ +# Factory + +Each entity should have a factory that helps to generate a new entity. +A factory must implements `App\Core\Factory\FactoryInterface`. + +## Implementation + +A factory is basically a service which contain at lease a method named `create`. + +``` +// src/Factory/MyEntityFactory; +namespace App\Factory; + +use App\Core\Factory\FactoryInterface; +use App\Entity\MyEntity; + +class MyEntityFactory implements FactoryInterface +{ + public function create(/* specify parameters if needed */): MyEntity + { + return new MyEntity(); + } +} +``` + +## Usage + +``` +// src/Controller/FooController.php +namespace App\Controller; + +use App\Factory\MyEntityFactory; +use Symfony\Component\HttpFoundation\Response; + +class FooController +{ + public function foo(MyEntityFactory $factory): Response + { + $entity = $factory->create(); + + // ... + } +} +``` diff --git a/docs/entities/query.md b/docs/entities/query.md new file mode 100644 index 0000000..37d5040 --- /dev/null +++ b/docs/entities/query.md @@ -0,0 +1,99 @@ +# Repository Query + +A Repository query is an abstraction of the doctrine repository. + +## Implementation + +Entities must implements `App\Core\Entity\EntityInterface`. + +``` +// src/Entity/MyEntity.php +namespace App\Entity; + +use App\Repository\MyEntityRepository; +use Doctrine\ORM\Mapping as ORM; +use App\Core\Entity\EntityInterface; + +/** + * @ORM\Entity(repositoryClass=MyEntityRepository::class) + */ +class MyEntity implements EntityInterface +{ + // ... +} +``` + +Each entity has its own repository query which is a service. + +``` +// src/Repository/MyEntityRepositoryQuery; +namespace App\Repository; + +use App\Core\Repository\RepositoryQuery; +use Knp\Component\Pager\PaginatorInterface; + +class MyEntityRepositoryQuery extends RepositoryQuery +{ + public function __construct(MyEntityRepository $repository, PaginatorInterface $paginator) + { + parent::__construct($repository, 'm', $paginator); + } + + // Example of custom filter + public function filterByFooBar(bool $foo, bool $bar): self + { + $this + ->andWhere('.foo = :foo') + ->andWhere('.bar = :bar') + ->setParameter(':foo', $foo) + ->setParameter(':bar', $bar); + + return $this; + } +} +``` + +## Usage + +You are able to find entities in an easy way, without knowing the identification variable and without creating a query builder. + +``` +// src/Controller/FooController.php +namespace App\Controller; + +use App\Repository\MyEntityRepositoryQuery +use Symfony\Component\HttpFoundation\Response; + +class FooController +{ + public function foo(MyEntityRepositoryQuery $query): Response + { + $entities = $query->create()->find(); + $entity = $query->create()->findOne(); + + // Filtered and sorted entities + $entities = $query->create() + ->orderBy('.id', 'DESC') + ->where('.isPublished = true') + ->find(); + + // ... + } +} +``` + +If you haved implemented specific methods: + +``` +$entities = $query->create() + ->filterByFoo($foo, $bar) + ->find(); +``` + +## Pager + +You can paginate entities (`Knp\Component\Pager\Pagination\PaginationInterface`): + +``` +$pager = $query->create()->paginate($page, $maxPerPage); +``` diff --git a/docs/settings/global.md b/docs/settings/global.md index 65ed67e..8dc1d47 100644 --- a/docs/settings/global.md +++ b/docs/settings/global.md @@ -1,7 +1,5 @@ # Global settings -![](/_static/img/settings/02.png) - ## Create settings The creation of settings is based on events. @@ -66,6 +64,10 @@ class SettingEventSubscriber extends EventSubscriber } ``` +Result: + +![](/_static/img/settings/02.png) + ## Access settings Settings are accessible using `App\Core\Setting\SettingManager` which is a service. diff --git a/docs/settings/navigation.md b/docs/settings/navigation.md index e7194b1..611309f 100644 --- a/docs/settings/navigation.md +++ b/docs/settings/navigation.md @@ -1,7 +1,5 @@ # Navigation settings -![](/_static/img/settings/03.png) - ## Create settings The creation of settings is based on events. @@ -67,6 +65,9 @@ class NavigationSettingEventSubscriber extends EventSubscriber } } ``` +Result: + +![](/_static/img/settings/03.png) ## Access settings @@ -82,13 +83,13 @@ use Symfony\Component\HttpFoundation\Response; class FooController { - public function foo(NavigationSettingManager $settingManager, SiteRequest $siteRequest): Response - { - $trackerCode = $settingManager->get($siteRequest->getNavigation(), 'nav_tracker_code'); - $contactEmail = $settingManager->get($siteRequest->getNavigation(), 'nav_contact_email'); + public function foo(NavigationSettingManager $settingManager, SiteRequest $siteRequest): Response + { + $trackerCode = $settingManager->get($siteRequest->getNavigation(), 'nav_tracker_code'); + $contactEmail = $settingManager->get('my_nav', 'nav_contact_email'); - // ... - } + // ... + } } ``` @@ -113,13 +114,13 @@ use Symfony\Component\HttpFoundation\Response; class FooController { - public function foo(NavigationSettingManager $settingManager, SiteRequest $siteRequest): Response - { - $settingManager->set($siteRequest->getNavigation(), 'nav_tracker_code', '...'); - $settingManager->set('my_nav', 'nav_contact_email', '...'); + public function foo(NavigationSettingManager $settingManager, SiteRequest $siteRequest): Response + { + $settingManager->set($siteRequest->getNavigation(), 'nav_tracker_code', '...'); + $settingManager->set('my_nav', 'nav_contact_email', '...'); - // ... - } + // ... + } } ``` diff --git a/docs/tasks.md b/docs/tasks.md index a15d5b0..50ebcf1 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -1,8 +1,6 @@ # Tasks -![](/img/tasks/01.png) - -Tasks are executable from UI. The creation of tasks is based on events. +Tasks are scripts executabled from UI. The creation of tasks is based on events. ``` // src/EventSuscriber/MyTaskEventSubscriber.php @@ -34,4 +32,6 @@ class MyTaskEventSubscriber extends TaskEventSubscriber } ``` -![](/img/tasks/02.png) +![](/_static/img/tasks/01.png) + +![](/_static/img/tasks/02.png) diff --git a/mkdocs.yml b/mkdocs.yml index 3bf1104..8682aba 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -10,6 +10,10 @@ nav: - Overview: tree/index.md - Navigation: tree/navigation.md - Menu: tree/menu.md + - Entities: + - 'Entity Manager': entities/em.md + - 'Repository Query': entities/query.md + - Factory: entities/factory.md - CRUD: - Overview: crud/index.md - Generator: crud/generator.md @@ -19,5 +23,5 @@ nav: - 'Navigation settings': settings/navigation.md - Users: - Users: users.md - - Others: + - Tasks: - Tasks: tasks.md