{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Welcome to Murph","text":"

Murph is an open-source CMF built on top of Symfony that helps you to build your own CMS with several domains and languages. It comes with a fully implemented and customizable tree manager, a CRUD generator, a 2FA authentication, settings and tasks managers, basic web analytics.

Symfony developers will love build on Murph \ud83d\udcaa End users will be fond of the interface and the powerful tools \ud83d\udc9c

Developed with love by Simon Vieille.

Support: Murph project on Matrix.

Access the demo.

"},{"location":"abtesting/","title":"A/B Testing","text":""},{"location":"abtesting/#overview","title":"Overview","text":"

Murph contains a basic tools to create A/B Tests.

The logic of the implement follows this logic:

"},{"location":"abtesting/#configure-the-node","title":"Configure the node","text":"

Go the navigation and edit the tested node:

"},{"location":"abtesting/#the-event-subscriber","title":"The Event Subscriber","text":"

The event subscriber helps you to define each variation and the TTL.

src/EventSubscriber/MyAbTestEventSubscriber.php
namespace App\\EventSubscriber;\n\nuse App\\Core\\EventSubscriber\\AbEventSubscriber as EventSubscriber;\nuse App\\Core\\Event\\Ab\\AbTestEvent;\n\nclass MyAbTestEventSubscriber extends EventSubscriber\n{\n    public function onInit(AbTestEvent $event)\n    {\n        if ($event->getTest()->getName() !== 'example_test') {\n            return;\n        }\n\n        $event->getTest()\n            ->addVariation('test_1', 'Value #1', 20) // 20% of chance\n            ->addVariation('test_2', 'Value #2', 30) // 30% of chance\n            ->addVariation('test_3', 'Value #3', 50) // 50% of chance\n            ->setDuration(3600 * 24) // duration of the cookie in seconds\n        ;\n    }\n\n    public function onRun(AbTestEvent $event)\n    {\n        // executed if a variation is newly picked\n    }\n}\n
"},{"location":"abtesting/#the-result","title":"The result","text":"

you can retrieve the test and the variation picked in PHP side and in template side.

use App\\Core\\Ab\\AbContainerInterface;\n\npublic function foo(AbContainerInterface $testContainer)\n{\n    if ($testContainer->has('example_test')) {\n        $test = $testContainer->get('example_test');\n\n        $result = $test->getResult(); // eg: \"test_2\"\n        $value = $test->getResultValue(); // eg: \"Value #2\"\n\n        // ...\n    }\n\n    // ...\n}\n
{% if ab_test_exists('example_test') %}\n    {% set test = ab_test('example_test') %}\n    {% set result = ab_test_result('example_test') %}\n    {% set value = ab_test_value('example_test') %}\n\n    {# ... #}\n{% endif %}\n
"},{"location":"abtesting/#global-ab-test","title":"Global A/B Test","text":"

If you need to perform an A/B test everywhere, you need to create a specific listener:

src/EventListener/CustomAbListener.php
namespace App\\EventListener;\n\nuse App\\Core\\EventListener\\AbListener as EventListener;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\HttpKernel\\Event\\RequestEvent;\n\nclass CustomAbListener extends EventListener\n{\n    /**\n     * {@inheritdoc}\n     */\n    protected function supports(Request $request): bool\n    {\n        return true;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getAbTestCode(): string\n    {\n        return 'my_global_ab_test_code';\n    }\n}\n

CustomAbListener must be registred:

config/services.yml
services:\n    # ...\n\n    App\\EventListener\\CustomAbListener;\n        tags:\n            - { name: kernel.event_listener, event: kernel.request }\n            - { name: kernel.event_listener, event: kernel.response }\n
"},{"location":"controller/","title":"Controller","text":""},{"location":"controller/#controller_1","title":"Controller","text":"

The default controller of a node is App\\Core\\Controller\\Site\\PageController::show. PageController extends Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController and implements very basic features: a Response builder which retrieves the good template and injects variables to the view.

To create a custom controller, do this way:

src/Controller/MyController.php
namespace App\\Controller;\n\nuse App\\Core\\Controller\\Site\\PageController;\n\nclass MyController extends PageController\n{\n    public function myAction()\n    {\n        if (!$this->siteRequest->getPage()) {\n            throw $this->createNotFoundException();\n        }\n\n        return $this->defaultRender($this->siteRequest->getPage()->getTemplate(), [\n            // view datas\n        ]);\n    }\n}\n

Then edit config/packages/app.yaml and add your controller:

core:\n    site:\n        controllers:\n            - {name: 'My action', action: 'App\\Controller\\MyController::myAction'}\n
"},{"location":"controller/#urlgenerator","title":"UrlGenerator","text":"

If your controller represents entities and if the associated node is visible in the sitemap, you can use a App\\Core\\Annotation\\UrlGenerator in annotations and implement a generator. See the example below.

src/UrlGenerator/MyEntityGenerator
namespace App\\UrlGenerator;\n\nuse App\\Core\\Entity\\Site\\Node;\nuse App\\Repository\\MyEntityRepositoryQuery;\nuse Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface;\n\nclass MyEntityGenerator\n{\n    protected MyEntityRepositoryQuery $query;\n    protected UrlGeneratorInterface $urlGenerator;\n\n    public function __construct(MyEntityRepositoryQuery $query, UrlGeneratorInterface $urlGenerator)\n    {\n        $this->query = $query;\n        $this->urlGenerator = $urlGenerator;\n    }\n\n    public function myActionGenerator(Node $node, array $options): array\n    {\n        $entities = $this->query->create()->find();\n\n        $urls = [];\n\n        foreach ($entities as $entity) {\n            $urls[] = $this->urlGenerator->generate(\n                $node->getRouteName(),\n                [\n                    'entity' => $entity->getId(),\n                    '_domain' => $options['_domain'],\n                ],\n                UrlGeneratorInterface::ABSOLUTE_URL\n            );\n        }\n\n        return $urls;\n    }\n}\n

Then, the have to annotate the controller like this (note: options is optional):

src/Controller/MyController.php
namespace App\\Controller;\n\nuse App\\Core\\Annotation\\UrlGenerator;\nuse App\\Core\\Controller\\Site\\PageController;\nuse App\\UrlGenerator\\MyEntityGenerator;\n\nclass MyController extends PageController\n{\n    #[UrlGenerator(service: MyEntityGenerator::class, method: 'myActionGenerator', options=[])]\n    public function myAction(MyEntity $entity)\n    {\n        // do stuff\n    }\n}\n

Finally, update config/services.yaml:

services:\n    # ...\n\n    App\\UrlGenerator\\MyEntityGenerator:\n        public: true\n
"},{"location":"procedure/","title":"Installation","text":""},{"location":"procedure/#setting-up-the-skeleton","title":"Setting up the skeleton","text":"

Depending of you environment, PHP and composer could be located in specific paths. Defines theme with environment vars:

export PHP_BIN=/usr/bin/php\nexport COMPOSER_BIN=/usr/bin/composer\nexport NPM_BIN=/usr/bin/npm\nexport YARN_BIN=/usr/bin/yarn\n

Create your project:

\"$COMPOSER_BIN\" create-project murph/murph-skeleton my_project ^1\n

An error occured because of the unconfigured database.

"},{"location":"procedure/#create-an-admin-user","title":"Create an admin user","text":"

Run \"$PHP_BIN\" bin/console murph:user:create and answer questions.

"},{"location":"procedure/#configure-a-web-server","title":"Configure a web server","text":"

Read the documentation of Symfony to configure a web server.

In case of a local server, you can use the Symfony Local Web Server. Then go to https://127.0.0.1:8000/admin.

"},{"location":"requirements/","title":"Technical Requirements","text":""},{"location":"sources/","title":"Sources codes","text":"

Source codes are accessible on Gitnet:

"},{"location":"tasks/","title":"Tasks","text":"

Tasks are scripts executabled from UI. The creation of tasks is based on events.

src/EventSubscriber/MyTaskEventSubscriber.php
namespace App\\EventSubscriber;\n\nuse App\\Core\\Event\\Task\\TaskInitEvent;\nuse App\\Core\\Event\\Task\\TaskRunRequestedEvent;\nuse App\\Core\\EventSubscriber\\Task\\TaskEventSubscriber;\n\nclass MyTaskEventSubscriber extends TaskEventSubscriber\n{\n    public function onInit(TaskInitEvent $event)\n    {\n        $event->addTask('my_task', 'Example', 'My task');\n    }\n\n    public function onRunRequest(TaskRunRequestedEvent $event)\n    {\n        if ('my_task' !== $event->getTask()) {\n            return;\n        }\n\n        $event->getOutput()->writeln('My task is started');\n\n        // ...\n\n        $event->getOutput()->writeln('My task is finished');\n    }\n}\n

"},{"location":"template/","title":"Templating","text":""},{"location":"template/#variables","title":"Variables","text":"

By default, these variables are given to a CMS view:

"},{"location":"template/#navigations-and-menus","title":"Navigations and menus","text":""},{"location":"template/#page","title":"Page","text":"

You can access a page's blocks this way:

{% set myBlock = _page.myBlock.value %}\n\n{{ myBlock }}\n
"},{"location":"template/#url-and-path","title":"URL and path","text":"

Murph has somes twig functions to manage URLs:

"},{"location":"template/#generic-functions","title":"Generic functions","text":""},{"location":"template/#node-functions","title":"Node functions","text":"

A node may have a disabled URL:

{% if not node.disableUrl %}\n    {% set path = safe_node_path(node) %}\n    {% set url = safe_node_url(node) %}\n{% endif %}\n

When the navigation has several domains, you can specify the domain:

{% set path = safe_node_path(node, {_domain: _domain}) %}\n{% set url = safe_node_url(node, {_domain: _domain}) %}\n
"},{"location":"template/#code-functions","title":"Code functions","text":""},{"location":"template/#filters","title":"Filters","text":"

When a content could contains tags (eg: '{{url://my_route}}), usemurph_url`. See the example below:

Code Output {{ content }} A link to the <a href=\"{{url://contact}}\">contact page</a> {{ content|murph_url }} A link to the <a href=\"https://example.com/contact\">contact page</a>"},{"location":"template/#string-builder","title":"String builder","text":"

The string builder builds a string using a format and an object or an array.

Examples:

In case of an not accessible property, no exception will be thrown.

"},{"location":"template/#file-attributes","title":"File attributes","text":"

Attributes are managed from the file manager. They are accessibles with these filters:

Code Result {{ '<img ... alt=\"{{fattr://hash/alt}}\">'|file_attributes }} <img ... alt=\"Attribute 'alt' of the file with the given hash\"> {{ 'path/to/file'|file_attribute('alt') }} Attribute alt of the given file"},{"location":"users/","title":"Users","text":"

Murph provided a basic authentication based on the User entity.

"},{"location":"changelog/core/","title":"Changelog","text":"[v1.24.0] - 2024-01-27

Added

Fixed

[v1.23.0] - 2023-11-01

Added

Changed

Fixed

[v1.22.0] - 2023-09-28

Added

[1.21.1] - 2023-08-17

Added

Fixed

[1.21.0] - 2023-08-11

Added

Fixed

[1.20.0] - 2023-07-27

Added

Fixed

Changed

[1.19.0] - 2023-04-15

Added

[1.18.0] - 2023-01-13

Added

Fixed

[1.17.1] - 2022-12-03

Fixed

[1.17.0] - 2022-11-19

Fixed

Changed

[1.16.0] - 2022-09-06

Added

Fixed

Changed

[1.15.0] - 2022-05-09

Added

Fixed

Changed

[1.14.1] - 2022-04-30

Added

Fixed

[1.14.0] - 2022-04-20

Added

Changed

[1.13.0] - 2022-04-17

Added

Fixed

Changed

[1.12.0] - 2022-03-26

Added

Fixed

Changed

[1.11.0] - 2022-03-22

Added

Changed

[1.10.0] - 2022-03-17

Added

Changed

[1.9.2] - 2022-03-14

Fixed

[1.9.1] - 2022-03-14

Added

Changed

[1.9.0] - 2022-03-13

Added

Changed

[1.8.0] - 2022-03-10

Added

Changed

[1.7.3] - 2022-03-06

Added

Fixed

[1.7.2] - 2022-03-03

Added

Fixed

[1.7.1] - 2022-03-01

Added

Fixed

[1.7.0] - 2022-03-01

Fixed

Changed

[1.6.0] - 2022-02-28

Added

Fixed

Changed

[1.5.0] - 2022-02-25

Added

Changed

[1.4.1] - 2022-02-23

Added

Fixed

Changed

[1.4.0] - 2022-02-21

Added

[1.3.0] - 2022-02-19

Added

Fixed

[1.2.0] - 2022-02-14

Added

Changed

[1.1.0] - 2022-02-29

Added

Fixed

Changed

[1.0.1] - 2022-02-25

Fixed

[1.0.0] - 2022-01-23"},{"location":"changelog/skeleton/","title":"Changelog","text":"[v1.23.0] - 2023-09-28

Changed

[v1.22.0] - 2023-09-28

Added

Fixed

Changed

[1.21.0] - 2023-08-11

Changed

[1.20.0] - 2023-07-27

Fixed

Added

Changed

[1.19.0] - 2023-04-15

Changed

[1.18.0] - 2023-01-13

Added

Fixed

[1.17.0] - 2022-11-19

Changed

[1.16.0]

Added

Fixed

Changed

[1.15.0]

Changed

[1.14.3]

Added

Changed

[1.14.2] [1.14.1]

Fixed

[1.14.0]

Changed

[1.13.0]

Changed

[1.12.0]

Changed

[1.11.0]

Changed

[1.10.0]

Added

Fixed

Changed

[1.9.1] - 2022-03-14

Added

Changed

[1.9.0] - 2022-03-13

Added

Changed

[1.8.0] - 2022-03-10

Added

Changed

[1.7.3] - 2022-03-06

Added

Fixed

[1.7.2] - 2022-03-03

Added

Fixed

[1.7.1] - 2022-03-01

Added

Fixed

[1.7.0] - 2022-03-01

Fixed

Changed

[1.6.0] - 2022-02-28

Added

Fixed

Changed

[1.5.0] - 2022-02-25

Added

Changed

[1.4.1] - 2022-02-23

Added

Fixed

Changed

[1.4.0] - 2022-02-21

Added

[1.3.0] - 2022-02-19

Added

Fixed

[1.2.0] - 2022-02-14

Added

Changed

[1.1.0] - 2022-02-29

Added

Fixed

Changed

[1.0.1] - 2022-02-25

Fixed

[1.0.0] - 2022-01-23"},{"location":"crud/","title":"CRUD","text":"

Murph helps you to manage specific entities with a CRUD manager:

You can configure almost anything:

"},{"location":"crud/configuration/","title":"Configuration","text":"

A generated crud controller contains a method named getConfiguration. This methods returns a instance of App\\Core\\Crud\\CrudConfiguration.

"},{"location":"crud/configuration/#setpagetitle","title":"setPageTitle","text":"

setPageTitle(string $page, string $title)

Set the title of the given page.

Example of usage in a CRUD template: <title>{{ configuration.pageTitle('index') }}</title>.

"},{"location":"crud/configuration/#setpageroute","title":"setPageRoute","text":"

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: <a href=\"{{ path(configuration.pageRoute('new')) }}\">...</a>.

"},{"location":"crud/configuration/#setform","title":"setForm","text":"

setForm(string $context, string $form,array$options = [])

Set the form used in the given context.

"},{"location":"crud/configuration/#setformoptions","title":"setFormOptions","text":"

setFormOptions(string $context,array$options = [])

Defines options given to a form.

"},{"location":"crud/configuration/#setaction","title":"setAction","text":"

setAction(string $page, string $action, bool|callable $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. Depending the context, the callable could receive the entity in parameter. Example:

->setAction('index', 'edit', function(EntityInterface $entity) {\n    return $entity->getUser()->getId() === $this->getUser()->getId();\n})\n

Usage in a CRUD template: {% if configuration.action('index', 'new')%}...{% endif %}.

"},{"location":"crud/configuration/#setactiontitle","title":"setActionTitle","text":"

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 }}

"},{"location":"crud/configuration/#setview","title":"setView","text":"

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"},{"location":"crud/configuration/#setviewdatas","title":"setViewDatas","text":"

setViewDatas(string $context,array$datas) and addViewData(string $context, string $name, $value)

Add datas given to a view. Useful in a custom controller.

"},{"location":"crud/configuration/#setfield","title":"setField","text":"

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;\n\n$configuration->setField('index', 'Title', Field\\TextField::class, [\n    // options\n])\n

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 default_value string null Default value to display when the property is null action string null An action to perform on click (null, edit, view) raw boolean false Render as HTML sort array | callable null Defines how to sort href string | callable null Data to generate a link href_attr array | callable null Attributes of the link inline_form null | callable null A method to define a form to edit datas inline_form_validation null | callable null A method to define a custom form validation callback Example #0
$configuration->setField('index', 'My field', TextField::class, [\n    'property' => 'myProperty',\n    // OR\n    'property_builder' => function($entity, array $options) {\n        return $entity->getMyProperty();\n    },\n])\n
Example #1
$configuration->setField('index', 'My field', TextField::class, [\n    'raw' => true,\n    'property_builder' => function($entity, array $options) {\n        return sprintf('<span class=\"foo\">%s</span>', $entity->getBar());\n    },\n])\n
Example #2
// https://127.0.0.7:8000/admin/my_entity?_sort=property&_sort_direction=asc\n$configuration->setField('index', 'My field', TextField::class, [\n    'property' => 'myProperty'\n    'sort' => ['property', '.myProperty'],\n    // OR\n    'sort' => ['property', function(MyEntityRepositoryQuery $query, $direction) {\n        $query->orderBy('.myProperty', $direction);\n    }],\n])\n
Example #3
use Symfony\\Component\\Form\\FormBuilderInterface;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType;\nuse Symfony\\Component\\Validator\\Constraints\\NotBlank;\n\n$configuration->setField('index', 'My field', TextField::class, [\n    'property' => 'myProperty',\n    'inline_form' => function(FormBuilderInterface $builder, EntityInterface $entity) {\n        $builder->add('myProperty', TextType::class, [\n            'required' => true,\n            'constraints' => [new NotBlank()],\n        ]);\n    }\n])\n
"},{"location":"crud/configuration/#textfield","title":"TextField","text":"

App\\Core\\Crud\\Field\\TextField

Option Type Default Description view string @Core/admin/crud/field/text.html.twig The templated rendered"},{"location":"crud/configuration/#datefield","title":"DateField","text":"

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"},{"location":"crud/configuration/#datetimefield","title":"DatetimeField","text":"

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"},{"location":"crud/configuration/#buttonfield","title":"ButtonField","text":"

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_attr_builder callabled null A callable data and used to generate button__attr button_tag string button HTML tag of the button"},{"location":"crud/configuration/#imagefield","title":"ImageField","text":"

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"},{"location":"crud/configuration/#booleanfield","title":"BooleanField","text":"

App\\Core\\Crud\\Field\\BooleanField

Option Type Default Description view string @Core/admin/crud/field/boolean.html.twig The templated rendered display string toggle Type of render (toggle or checkbox) checkbox_class_when_true string fa-check-square HTML class added when the value is true and display is checkbox checkbox_class_when_false string fa-square HTML class added when the value is false and display is checkbox toggle_class_when_true string bg-success HTML class added when the value is true and display is toggle toggle_class_when_false string bg-secondary HTML class added when the value is false and display is toggle"},{"location":"crud/configuration/#setmaxperpage","title":"setMaxPerPage","text":"

setMaxPerPage(string $page, int $max)

Set how many elements are displayed in a single page.

"},{"location":"crud/configuration/#seti18n","title":"setI18n","text":"

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.

"},{"location":"crud/configuration/#setdefaultsort","title":"setDefaultSort","text":"

setDefaultSort(string $context, string $label, string $direction = 'asc')

Set the default sort applied in the repository query.

$configuration\n    ->setDefaultSort('index', 'title', 'asc')\n    ->setField('index', 'Title', Field\\TextField::class, [\n        'property' => 'title',\n        'sort' => ['title', '.title'],\n    ]);\n
"},{"location":"crud/configuration/#setissortablecollection","title":"setIsSortableCollection","text":"

setIsSortableCollection(string $page, bool $isSortableCollection)

It enables the drag & drop to sort entities.

class MyEntity implements EntityInterface\n{\n    // ...\n\n    /**\n     * @ORM\\Column(type=\"integer\", nullable=true)\n     */\n    private $sortOrder;\n\n    public function getSortOrder(): ?int\n    {\n        return $this->sortOrder;\n    }\n\n    public function setSortOrder(?int $sortOrder): self\n    {\n        $this->sortOrder = $sortOrder;\n\n        return $this;\n    }\n\n    // ...\n}\n
"},{"location":"crud/configuration/#setsortablecollectionproperty","title":"setSortableCollectionProperty","text":"

setSortableCollectionProperty(string $sortableCollectionProperty)

In order to sort entities, the default property used is sortOrder. You can set something else.

"},{"location":"crud/configuration/#setbatchaction","title":"setBatchAction","text":"

setBatchAction(string $context, string $action, string $label, callable $callack)

Add a batch action. The callback has 2 arguments:

use App\\Core\\Entity\\EntityInterface;\nuse App\\Core\\Manager\\EntityManager;\n\n$configuration->setBatchAction(\n    'index',\n    'delete',\n    'Delete',\n    function(EntityInterface $entity, EntityManager $manager) {\n        $manager->delete($entity);\n    }\n);\n
"},{"location":"crud/configuration/#setglobalbatchaction","title":"setGlobalBatchAction","text":"

setGlobalBatchAction(string $context, string $action, string $label, callable $callack)

Add a global batch action. The callback has 3 arguments:

Do not use the same action in global and classic batch action.

The callback can return a response. If not, the user will be redirect automatically. See the example below:

use App\\Core\\Entity\\RepositoryQuery;\nuse App\\Core\\Manager\\EntityManager;\nuse Symfony\\Component\\HttpFoundation\\JsonResponse;\n\n$configuration->setGlobalBatchAction(\n    'index',\n    'export_json',\n    'Export to JSON',\n    function(RepositoryQuery $query, EntityManager $manager, ?array $selection): JsonResponse {\n        $items = $selection ?? $query->find();\n\n        return $this->json($items);\n    }\n);\n
"},{"location":"crud/generator/","title":"Generator","text":""},{"location":"crud/generator/#prepare-your-entity","title":"Prepare your entity","text":""},{"location":"crud/generator/#generation","title":"Generation","text":"

The generation is performed in CLI. These information are required:

Simply run php bin/console make:crud-controller.

"},{"location":"entities/em/","title":"Entity Manager","text":"

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.

"},{"location":"entities/em/#implementation","title":"Implementation","text":"

Entities must implements App\\Core\\Entity\\EntityInterface.

src/Entity/MyEntity.php
namespace App\\Entity;\n\nuse App\\Repository\\MyEntityRepository;\nuse Doctrine\\ORM\\Mapping as ORM;\nuse App\\Core\\Entity\\EntityInterface;\n\n#[ORM\\Entity(repositoryClass: MyEntityRepository::class)]\nclass MyEntity implements EntityInterface\n{\n    // ...\n}\n
"},{"location":"entities/em/#usage","title":"Usage","text":"

There are 2 entity managers which are services:

src/Controller/FooController.php
namespace App\\Controller;\n\nuse App\\Core\\Manager\\EntityManager\nuse App\\Entity\\MyEntity;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n    public function foo(EntityManager $entityManager): Response\n    {\n        $myEntity = new MyEntity();\n\n        // Creates an entity\n        $entityManager->create($myEntity);\n\n        // Updates an entity\n        $entityManager->update($myEntity);\n\n        // Deletes an entity\n        $entityManager->delete($myEntity);\n\n        // ...\n    }\n}\n
"},{"location":"entities/em/#events","title":"Events","text":"

Events are dispatched before and after creation, update and deletion. All entities of Muprh use the entity manager.

src/EventSubscriber/MyEntityEventSubscriber.php
namespace App\\EventSubscriber;\n\nuse App\\Core\\Entity\\EntityInterface;\nuse App\\Core\\Event\\EntityManager\\EntityManagerEvent;\nuse App\\Core\\EventSubscriber\\EntityManagerEventSubscriber;\nuse App\\Entity\\MyEntity;\n\nclass MyEntityEventSubscriber extends EntityManagerEventSubscriber\n{\n    public function supports(EntityInterface $entity): bool\n    {\n        return $entity instanceof MyEntity;\n    }\n\n    public function onCreate(EntityManagerEvent $event)\n    {\n        if (!$this->supports($event->getEntity())) {\n            return;\n        }\n\n        // ...\n    }\n\n    public function onUpdate(EntityManagerEvent $event)\n    {\n        if (!$this->supports($event->getEntity())) {\n            return;\n        }\n\n        // ...\n    }\n\n    public function onDelete(EntityManagerEvent $event)\n    {\n        if (!$this->supports($event->getEntity())) {\n            return;\n        }\n\n        // ...\n    }\n\n    public function onPreCreate(EntityManagerEvent $event)\n    {\n        if (!$this->supports($event->getEntity())) {\n            return;\n        }\n\n        // ...\n    }\n\n    public function onPreUpdate(EntityManagerEvent $event)\n    {\n        if (!$this->supports($event->getEntity())) {\n            return;\n        }\n\n        // ...\n    }\n\n    public function onPreDelete(EntityManagerEvent $event)\n    {\n        if (!$this->supports($event->getEntity())) {\n            return;\n        }\n\n        // ...\n    }\n}\n
"},{"location":"entities/factory/","title":"Factory","text":"

Each entity should have a factory that helps to generate a new entity. A factory must implements App\\Core\\Factory\\FactoryInterface.

"},{"location":"entities/factory/#generation","title":"Generation","text":"

A factory is basically a service which contain at lease a method named create.

The generation is performed in CLI. These information are required:

Simply run php bin/console make:factory.

"},{"location":"entities/factory/#usage","title":"Usage","text":"src/Controller/FooController.php
namespace App\\Controller;\n\nuse App\\Factory\\MyEntityFactory;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n    public function foo(MyEntityFactory $factory): Response\n    {\n        $entity = $factory->create();\n\n        // ...\n    }\n}\n
"},{"location":"entities/query/","title":"Repository Query","text":"

A Repository query is an abstraction of the doctrine repository.

"},{"location":"entities/query/#requirement","title":"Requirement","text":"

Entities must implements App\\Core\\Entity\\EntityInterface.

src/Entity/MyEntity.php
namespace App\\Entity;\n\nuse App\\Repository\\MyEntityRepository;\nuse Doctrine\\ORM\\Mapping as ORM;\nuse App\\Core\\Entity\\EntityInterface;\n\n#[ORM\\Entity(repositoryClass: MyEntityRepository::class)]\nclass MyEntity implements EntityInterface\n{\n    // ...\n}\n
"},{"location":"entities/query/#generation","title":"Generation","text":"

The generation is performed in CLI. These information are required:

Simply run php bin/console make:repository-query.

Each entity has its own repository query which is a service.

src/Repository/MyEntityRepositoryQuery
namespace App\\Repository;\n\nuse App\\Core\\Repository\\RepositoryQuery;\nuse Knp\\Component\\Pager\\PaginatorInterface;\n\nclass MyEntityRepositoryQuery extends RepositoryQuery\n{\n    public function __construct(MyEntityRepository $repository, PaginatorInterface $paginator)\n    {\n        parent::__construct($repository, 'm', $paginator);\n    }\n\n    // Example of custom filter\n    public function filterByFooBar(bool $foo, bool $bar): self\n    {\n        $this\n            ->andWhere('.foo = :foo')\n            ->andWhere('.bar = :bar')\n            ->setParameter(':foo', $foo)\n            ->setParameter(':bar', $bar);\n\n        return $this;\n    }\n}\n
"},{"location":"entities/query/#usage","title":"Usage","text":"

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;\n\nuse App\\Repository\\MyEntityRepositoryQuery\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n    public function foo(MyEntityRepositoryQuery $query): Response\n    {\n        $entities = $query->create()->find();\n        $entity = $query->create()->findOne();\n\n        // Filtered and sorted entities\n        $entities = $query->create()\n            ->orderBy('.id', 'DESC')\n            ->where('.isPublished = true')\n            ->find();\n\n        // ...\n    }\n}\n
"},{"location":"entities/query/#custom-methods","title":"Custom methods","text":"
// ...\n\nclass MyEntityRepositoryQuery extends RepositoryQuery\n{\n    // ...\n\n    public function filterByFooBar(bool $foo, bool $bar): self\n    {\n        $this\n            ->andWhere('.foo = :foo')\n            ->andWhere('.bar = :bar')\n            ->setParameter(':foo', $foo)\n            ->setParameter(':bar', $bar);\n\n        return $this;\n    }\n}\n
$entities = $query->create()\n    ->filterByFoo($foo, $bar)\n    ->find();\n

In the context of a CRUD, filters are applied using the method useFilters. Integers, strings and booleans are automatically processed. Other types are passed to the method filterHandler.

You have to override it to manage them, example:

// ...\n\nuse App\\Entity\\Something;\n\nclass MyEntityRepositoryQuery extends RepositoryQuery\n{\n    // ...\n\n    public function filterHandler(string $name, $value): self\n    {\n        if ($name === 'something' && $value instanceof Something) {\n            $this\n                ->join('something', 's')\n                ->andWhere('s.id = :something')\n                ->setParameter('something', $value->getId())\n            ;\n        }\n    }\n}\n

You can also force filterHandler te be used for specific filter field:

// ...\n\nclass MyEntityRepositoryQuery extends RepositoryQuery\n{\n    public function __construct(Repository $repository, PaginatorInterface $paginator)\n    {\n        // ...\n\n        $this->addForcedFilterHandler('foo');\n    }\n\n    public function filterHandler(string $name, $value): self\n    {\n        // ...\n\n        if ($name === 'foo) {\n            // ...\n        }\n    }\n\n}\n
"},{"location":"entities/query/#pager","title":"Pager","text":"

You can paginate entities (Knp\\Component\\Pager\\Pagination\\PaginationInterface):

$pager = $query->create()->paginate($page, $maxPerPage);\n
"},{"location":"settings/global/","title":"Global settings","text":""},{"location":"settings/global/#create-settings","title":"Create settings","text":"

The creation of settings is based on events.

Using an event subscriber, you can create settings and define how to edit them. A setting's value is stored in json so a value could be a string, a boolean, an array, etc.

See the example below.

src/EventSubscriber/SettingEventSubscriber.php
namespace App\\EventSubscriber;\n\nuse App\\Core\\Event\\Setting\\SettingEvent;\nuse App\\Core\\EventSubscriber\\SettingEventSubscriber as EventSubscriber;\nuse App\\Core\\Setting\\SettingManager;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\CheckboxType;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\ColorType;\n\nclass SettingEventSubscriber extends EventSubscriber\n{\n    protected SettingManager $manager;\n\n    public function __construct(SettingManager $manager)\n    {\n        $this->manager = $manager;\n    }\n\n    public function onInit(SettingEvent $event)\n    {\n        $this->manager->init('app_font_color', 'Design', 'Font color', , '#fff');\n        $this->manager->init('app_background_color', 'Design', 'Background color', '#333');\n        $this->manager->init('app_maintenance_enabled', 'System', 'Maintenance', false);\n    }\n\n    public function onFormInit(SettingEvent $event)\n    {\n        $data = $event->getData();\n        $builder = $data['builder'];\n        $entity = $data['entity'];\n\n        if (in_array($entity->getCode(), ['app_font_color', 'app_background_color'])) {\n            $builder->add(\n                'value',\n                ColorType::class,\n                [\n                    'label' => $entity->getLabel(),\n                ]\n            );\n        }\n\n        if (in_array($entity->getCode(), ['app_maintenance_enabled'])) {\n            $builder->add(\n                'value',\n                CheckboxType::class,\n                [\n                    'label' => $entity->getLabel(),\n                ]\n            );\n        }\n    }\n}\n

Result:

"},{"location":"settings/global/#access-settings","title":"Access settings","text":"

Settings are accessible using App\\Core\\Setting\\SettingManager which is a service.

src/Controller/FooController.php
namespace App\\Controller;\n\nuse App\\Core\\Setting\\SettingManager;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n    public function foo(SettingManager $settingManager): Response\n    {\n        $fontColor = $settingManager->get('app_font_color');\n        $backgroundColor = $settingManager->get('app_background_color');\n        $maintenanceEnabled = $settingManager->get('app_maintenance_enabled');\n\n        // ...\n    }\n}\n

In a template, you can use the function setting:

Font color: {{ setting('app_font_color') }}<br>\nBackground color: {{ setting('app_background_color') }}<br>\nMaintenance enabled: {{ setting('app_maintenance_enabled') ? 'Yes' : 'No' }}<br>\n
"},{"location":"settings/global/#update-settings","title":"Update settings","text":"

Settings are accessible using App\\Core\\Setting\\SettingManager which is a service.

src/Controller/FooController.php
namespace App\\Controller;\n\nuse App\\Core\\Setting\\SettingManager;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n    public function foo(SettingManager $settingManager): Response\n    {\n        $settingManager->set('app_font_color', '#f00');\n        $settingManager->set('app_background_color', '#00f');\n        $settingManager->set('app_maintenance_enabled', true);\n\n        // ...\n    }\n}\n

You can also edit them from UI:

"},{"location":"settings/global/#options","title":"Options","text":"

You can add options using this way:

$event->setOption('view', 'large');\n

Available options:

"},{"location":"settings/navigation/","title":"Navigation settings","text":""},{"location":"settings/navigation/#create-settings","title":"Create settings","text":"

The creation of settings is based on events.

Using an event subscriber, you can create settings and define how to edit them. A setting's value is stored in json so a value could be a string, a boolean, an array, etc.

See the example below.

src/EventSubscriber/NavigationSettingEventSubscriber.php

namespace App\\EventSubscriber;\n\nuse App\\Core\\Event\\Setting\\NavigationSettingEvent;\nuse App\\Core\\EventSubscriber\\NavigationSettingEventSubscriber as EventSubscriber;\nuse App\\Core\\Setting\\NavigationSettingManager;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\EmailType;\n\nclass NavigationSettingEventSubscriber extends EventSubscriber\n{\n    protected NavigationSettingManager $manager;\n\n    public function __construct(NavigationSettingManager $manager)\n    {\n        $this->manager = $manager;\n    }\n\n    public function onInit(NavigationSettingEvent $event)\n    {\n        $data = $event->getData();\n        $navigation = $data['navigation'];\n\n        $this->manager->init($navigation, 'nav_tracker_code', 'Stats', 'Tracker', '');\n        $this->manager->init($navigation, 'nav_contact_email', 'Contact', 'Email', 'foo@example.com');\n    }\n\n    public function onFormInit(NavigationSettingEvent $event)\n    {\n        $data = $event->getData();\n        $builder = $data['builder'];\n        $entity = $data['entity'];\n\n        if (in_array($entity->getCode(), ['nav_tracker_code'])) {\n            $builder->add(\n                'value',\n                TextType::class,\n                [\n                    'label' => $entity->getLabel(),\n                ]\n            );\n        }\n\n        if (in_array($entity->getCode(), ['nav_contact_email'])) {\n            $builder->add(\n                'value',\n                EmailType::class,\n                [\n                    'label' => $entity->getLabel(),\n                ]\n            );\n        }\n    }\n}\n
Result:

"},{"location":"settings/navigation/#access-settings","title":"Access settings","text":"

Settings are accessible using App\\Core\\Setting\\NavigationSettingManager which is a service.

src/Controller/FooController.php
namespace App\\Controller;\n\nuse App\\Core\\Setting\\NavigationSettingManager;\nuse App\\Core\\Site\\SiteRequest;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n    public function foo(NavigationSettingManager $settingManager, SiteRequest $siteRequest): Response\n    {\n        $trackerCode = $settingManager->get($siteRequest->getNavigation(), 'nav_tracker_code');\n        $contactEmail = $settingManager->get('my_nav', 'nav_contact_email');\n\n        // ...\n    }\n}\n

In a template, you can use the function navigation_setting:

Tracker code: {{ navigation_setting(_navigation, 'nav_tracker_code') }}<br>\nContact email: {{ navigation_setting('my_nav', 'nav_contact_email') }}<br>\n
"},{"location":"settings/navigation/#update-settings","title":"Update settings","text":"

Settings are accessible using App\\Core\\Setting\\NavigationSettingManager which is a service.

src/Controller/FooController.php
namespace App\\Controller;\n\nuse App\\Core\\Setting\\NavigationSettingManager;\nuse App\\Core\\Site\\SiteRequest;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n    public function foo(NavigationSettingManager $settingManager, SiteRequest $siteRequest): Response\n    {\n        $settingManager->set($siteRequest->getNavigation(), 'nav_tracker_code', '...');\n        $settingManager->set('my_nav', 'nav_contact_email', '...');\n\n        // ...\n    }\n}\n

You can also edit them from UI:

"},{"location":"tree/","title":"Tree manager","text":"

Murph manages contents this way:

"},{"location":"tree/#diagram","title":"Diagram","text":"
%%{\n  init: {\n    \"theme\": \"dark\",\n    \"flowchart\": {\n      \"curve\": \"cardinal\"\n    }\n  }\n}%%\n\ngraph TB\n  N1[Navigation 1] --> M1[Menu 1];\n  N2[Navigation ...];\n  NX[Navigation N];\n\n  N1 --> M2[Menu ...];\n  N1 --> MX[Menu N];\n\n  N2 --> L1[...]\n  NX --> L2[...]\n\n  M1 --> MN1[Node 1]\n  M1 --> MN2[Node ...]\n  M1 --> MN3[Node N]\n\n  M2 --> L3[...]\n  MX --> L4[...]\n\n  MN1 --> P1[Page]\n\n  P1 --> B1[Block 1]\n  P1 --> B2[Block ...]\n  P1 --> BN[Block N]
"},{"location":"tree/menu/","title":"Menu","text":"

To create a menu, go to Trees, select the navigation and click on Add a menu. Then fill the form and save.

When a menu is created then an node is automatically generated.

"},{"location":"tree/navigation/","title":"Navigation","text":"

To create a navigation, go to Navigations and click on New. Then fill the form and save.

If several navigations share the same domain, then the locale will by used to prefix routes. But if a navigation uses a single domain then the local will not prefix routes.

"},{"location":"tree/node/","title":"Node","text":"

A node allows you to create a tree structure in a menu. To create or update a node, click on \"Edit\" or the sign \"+\". The basic information to fill is label but more parameters are accessible via 4 tabs:

"},{"location":"tree/node/#content","title":"Content","text":"

The content tab allow you to define an optional Page to associate to the node. You can also define that the node is an alias of another node.

"},{"location":"tree/node/#routing","title":"Routing","text":"

The routing tab is very important. It allows you to define all parameters related to:

To add a controller in the list, edit config/packages/app.yaml:

core:\n    site:\n        controllers:\n            - {name: 'Foo', action: 'App\\Controller\\ExampleController::foo'}\n            - {name: 'Bar', action: 'App\\Controller\\OtherController::bar'}\n

If you need to restrict the access, you can provided a list of roles in the configuration:

config/packages/app.yaml
core:\n    site:\n        security:\n            roles:\n                - {name: 'Foo role', role: 'ROLE_FOO'}\n                - {name: 'Bar role', role: 'ROLE_BAR'}\n

Then you will be able to select what roles are required:

"},{"location":"tree/node/#attributes","title":"Attributes","text":"

Attributes are a collection of keys and values attached to a node (eg: class, icon, whatever you want).

"},{"location":"tree/node/#sitemap","title":"Sitemap","text":"

This tab contains information to configure the sitemap.

"},{"location":"tree/page/","title":"Page","text":"

A page is a doctrine entity that contains blocks and form builder. You can run php bin/console make:page and generate a new page in an interactive way.

src/Entity/Page/YourPage.php
namespace App\\Entity\\Page;\n\nuse App\\Core\\Entity\\Site\\Page\\Block;\nuse App\\Core\\Entity\\Site\\Page\\Page;\nuse App\\Core\\Form\\Site\\Page\\TextBlockType;\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\n#[ORM\\Entity]\nclass YourPage extends Page\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder->add(\n            'myBlock',\n            TextBlockType::class,\n            [\n                'label' => 'My block',\n                'options' => [\n                    // options given to the sub form\n                ],\n            ]\n        );\n\n        // ...\n    }\n\n    public function setMyBlock(Block $block)\n    {\n        return $this->setBlock($block);\n    }\n\n    public function getMyBlock(): Block\n    {\n        return $this->getBlock('myBlock');\n    }\n\n    // ...\n}\n

Then edit config/packages/app.yaml and add your page:

core:\n    site:\n        pages:\n            App\\Entity\\Page\\SimplePage:\n                name: 'Simple page'\n                templates:\n                    - {name: \"Default\", file: \"page/simple/default.html.twig\"}\n            App\\Entity\\Page\\YourPage:\n                name: 'Your page'\n                templates:\n                    - {name: \"Template 1\", file: \"page/your_page/template1.html.twig\"}\n                    - {name: \"Template 2\", file: \"page/your_page/template2.html.twig\"}\n
"},{"location":"tree/page/#blocks","title":"Blocks","text":""},{"location":"tree/page/#textblocktype","title":"TextBlockType","text":"

App\\Core\\Form\\Site\\Page\\TextBlockType will render a symfony TextType.

"},{"location":"tree/page/#textareablocktype","title":"TextareaBlockType","text":"

App\\Core\\Form\\Site\\Page\\TextareaBlockType will render a symfony TextareaType.

"},{"location":"tree/page/#choiceblocktype","title":"ChoiceBlockType","text":"

App\\Core\\Form\\Site\\Page\\ChoiceBlockType will render a symfony ChoiceType.

"},{"location":"tree/page/#fileblocktype","title":"FileBlockType","text":"

App\\Core\\Form\\Site\\Page\\FileBlockType will render a symfony FileType with a download link.

In the getter, you must specify the block:

use App\\Core\\Entity\\Site\\Page\\FileBlock;\n\npublic function getMyBlock(): Block\n{\n    return $this->getBlock('myBlock', FileBlock::class);\n}\n
"},{"location":"tree/page/#filepickerblocktype","title":"FilePickerBlockType","text":"

App\\Core\\Form\\Site\\Page\\FilePickerBlockType will render a specific widget that use the file manager.

"},{"location":"tree/page/#editorjstextareablocktype","title":"EditorJsTextareaBlockType","text":"

App\\Core\\Form\\Site\\Page\\EditorJsTextareaBlockType will render a EditorJs widget.

"},{"location":"tree/page/#grapesjsblocktype","title":"GrapesJsBlockType","text":"

App\\Core\\Form\\Site\\Page\\GrapesJsBlockType will render a GrapesJS editor.

"},{"location":"tree/page/#tinymcetextareablocktype","title":"TinymceTextareaBlockType","text":"

App\\Core\\Form\\Site\\Page\\TinymceTextareaBlockType will render a Tinymce editor.

"},{"location":"tree/page/#imageblocktype","title":"ImageBlockType","text":"

App\\Core\\Form\\Site\\Page\\ImageBlockType will render a symfony FileType with a preview of the image.

In the getter, you must specify the block:

use App\\Core\\Entity\\Site\\Page\\FileBlock;\n\npublic function getMyBlock(): Block\n{\n    return $this->getBlock('myBlock', FileBlock::class);\n}\n
"},{"location":"tree/page/#collectionblocktype","title":"CollectionBlockType","text":"

App\\Core\\Form\\Site\\Page\\CollectionBlockType will a render a symfony CollectionType with availabity to add and remove elements.

use App\\Core\\Entity\\Site\\Page\\CollectionBlock;\n\npublic function getMyBlock(): Block\n{\n    return $this->getBlock('myBlock', CollectionBlock::class);\n}\n
"},{"location":"tree/page/#event","title":"Event","text":"

When a page is being edited, the options can be set as follows:

src/EventSubscriber/PageEventSubscriber.php
namespace App\\EventSubscriber;\n\nuse App\\Core\\Event\\Page\\PageEditEvent;\nuse App\\Entity\\Page\\YourPage;\n\nclass PageEventSubscriber implements EventSubscriberInterface\n{\n    public static function getSubscribedEvents()\n    {\n        return [\n            PageEditEvent::FORM_INIT_EVENT => ['onFormInit'],\n        ];\n    }\n\n    public function onFormInit(PageEditEvent $event)\n    {\n        if ($event->getPage() instanceof YourPage) {\n            $event->addPageBuilderOptions([\n                // options\n            ]);\n        }\n    }\n}\n
"},{"location":"utils/cache/","title":"Cache Manager","text":""},{"location":"utils/doctrine/","title":"Doctrine","text":""},{"location":"utils/doctrine/#timestampable","title":"Timestampable","text":"

App\\Core\\Doctrine\\Timestampable is a trait usuble in an entity. It adds createdAt and updatedAt datetime attributes with the setters and the getters :

When the entity is created or updated, createdAt and updatedAt are automatically updated to.

"},{"location":"utils/doctrine/#usage","title":"Usage","text":"src/Entity/FooEntity.php
namespace App/Entity;\n\nuse use App\\Core\\Entity\\EntityInterface;\nuse Doctrine\\ORM\\Mapping as ORM;\n\nuse App\\Core\\Doctrine\\Timestampable;\nuse App\\Core\\Entity\\EntityInterface;\nuse App\\Repository\\FooRepository;\nuse Doctrine\\ORM\\Mapping as ORM;\n\n#[ORM\\Entity(repositoryClass: FooRepository::class)]\n#[ORM\\HasLifecycleCallbacks]\nclass FooEntity implements EntityInterface\n{\n    use Timestampable;\n\n    // ...\n}\n
"},{"location":"utils/file_attribute/","title":"File attribute","text":"

App\\Core\\File\\FileAttribute::handleFile transforms a file path to an instance of Symfony\\Component\\HttpFoundation\\File\\File. You can specify another class if needed. If the path is null or if the file does not exist, it returns null.

use App\\Core\\File\\FileAttribute;\nuse App\\Foo\\Bar;\n\n$path = 'path/to/file';\n$file = FileAttribute::handleFile($path); // returns an instance of File\n\n$path = 'path/to/file';\n$file = FileAttribute::handleFile($path, Bar::class); // returns an instance of Bar\n\n$path = 'path/to/removed_file';\n$file = FileAttribute::handleFile($path); // returns null\n\n$path = null;\n$file = FileAttribute::handleFile($path); // returns null\n
"},{"location":"utils/file_handler/","title":"File upload handler","text":"

App\\Core\\Form\\FileUploadHandler is a service and helps you to upload a file. See example below.

use App\\Core\\Form\\FileUploadHandler;\nuse App\\Entity\\Foo;\nuse App\\Form\\FooType;\nuse Symfony\\Component\\HttpFoundation\\Request;\n\npublic function upload(Request $request, FileUploadHandler $fileUpload)\n{\n    $entity = new Foo();\n    $form = $this->createForm(FooType::class, $foo);\n\n    if ($request->isMethod('POST')) {\n        $form->handleRequest($request);\n\n        if ($form->isValid()) {\n            $fileDirectory = 'uploads/';\n            $keepOriginalFilename = false;\n\n            $fileUpload->handleForm(\n                uploadedFile: $form->get('image')->getData(), // Symfony\\Component\\HttpFoundation\\File\\UploadedFile or null\n                path: $fileDirectory,\n                // optional\n                afterUploadCallback: function ($filename) use ($entity, $fileDirectory) {\n                    $entity->setImage($fileDirectory.$filename);\n                },\n                // optional\n                keepOriginalFilename: $keepOriginalFilename\n            );\n\n            // ...\n        }\n    }\n}\n

If you need to generate custom filenames, FileUploadHandler allows you to define a generator:

use Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\n$fileUpload->setFilenameGenerator(function(UploadedFile $file) {\n    return sprintf('%d.%s', mt_rand(), $file->guessExtension());\n});\n
"},{"location":"utils/mail/","title":"Mail","text":"

App\\Core\\Notification\\MailNotifier is a service and helps you to create a mail using twig template.

Useful methods:

Exemple:

use App\\Core\\Notification\\MailNotifier;\nuse App\\Repository\\UserRepositoryQuery;\n\npublic function foo(MailNotifier $notifier, UserRepositoryQuery $query)\n{\n    // ...\n\n    $notifier\n        ->init()\n        ->setSubject('Your bill')\n        ->addRecipient('john.doe@example.com')\n        ->addRecipients(array_map(\n            fn($u) => $u->getEmail(),\n            $query->create()->where('.isAdmin = true')->find()\n        ), true)\n        ->addAttachment('path/to/bill.pdf')\n        ->notify('mail/bill.html.twig', [\n            // view params\n        ])\n    ;\n}\n
"},{"location":"utils/slug/","title":"Slug","text":"

Murph requires cocur/slugify. See the official documentation on Github.

"},{"location":"utils/editors/editorjs/","title":"Editor.js","text":"

Workspace in classic editors is made of a single contenteditable element, used to create different HTML markups. Editor.js workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor's Core.

Editor.js is fully integrated in Murph as form types.

"},{"location":"utils/editors/editorjs/#classic-form","title":"Classic form","text":"
// src/Form/ExampleType.php\nnamespace App\\Form\\ExampleType;\n\nuse App\\Core\\Form\\Type\\EditorJsTextareaType;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\nclass ExampleType extends AbstractType\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder->add(\n            'myField',\n            EditorJsTextareaType::class\n        );\n\n        // ...\n    }\n\n    // ...\n}\n

Modified data should return stringified JSON array if empty:

public function getMyField(): string\n{\n    if (empty($this->myField)) {\n        $this->myField = '[]';\n    }\n\n    return $this->myField;\n}\n
"},{"location":"utils/editors/editorjs/#page-form","title":"Page form","text":"
// src/Entity/Page/YourPage.php\nnamespace App\\Entity\\Page;\n\nuse App\\Core\\Entity\\Site\\Page\\Block;\nuse App\\Core\\Form\\Site\\Page\\EditorJsTextareaBlockType;\n\n#[@ORM\\Entity]\nclass YourPage extends Page\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder->add(\n            'myBlock',\n            EditorJsTextareaBlockType::class,\n            [\n                'label' => 'My block',\n                'row_attr' => [\n                ],\n                'options' => [\n                    // options given to the sub form\n                ],\n            ]\n        );\n\n        // ...\n    }\n\n    public function setMyBlock(Block $block)\n    {\n        return $this->setBlock($block);\n    }\n\n    public function getMyBlock(): Block\n    {\n        return $this->getBlock('myBlock');\n    }\n\n    // ...\n}\n
"},{"location":"utils/editors/editorjs/#rendering","title":"Rendering","text":"

Editor.js will generate a JSON which contains blocks.

Supported blocks:

To render HTML, the basic way is: {{ value|editorjs_to_html }} If you want to render specific blocks: {{ value|editorjs_to_html(['paragraph', 'header', ...])) }}

Block have default templates stored in vendor/murph/murph-core/src/core/Resources/views/editorjs. They can be simply overridden in config/packages/app.yaml:

core:\n    editor_js:\n        blocks:\n            paragraph: 'path/to/paragraph.html.twig'\n            header: 'path/to/header.html.twig'\n
"},{"location":"utils/editors/grapesjs/","title":"GrapesJS","text":"

GrapesJS is a web builder which combines different tools and features with the goal to help users to build HTML templates without any knowledge of coding. It's a very good solution to replace the common WYSIWYG editor like TinyMCE.

GrapesJS is fully integrated in Murph as form types.

"},{"location":"utils/editors/grapesjs/#classic-form","title":"Classic form","text":"
// src/Form/ExampleType.php\nnamespace App\\Form\\ExampleType;\n\nuse App\\Core\\Form\\Type\\GrapesJsType;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\nclass ExampleType extends AbstractType\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder->add(\n            'myField',\n            GrapesJsType::class\n        );\n\n        // ...\n    }\n\n    // ...\n}\n
"},{"location":"utils/editors/grapesjs/#page-form","title":"Page form","text":"
// src/Entity/Page/YourPage.php\nnamespace App\\Entity\\Page;\n\nuse App\\Core\\Entity\\Site\\Page\\Block;\nuse App\\Core\\Form\\Site\\Page\\GrapesJsBlockType;\n\n#[ORM\\Entity]\nclass YourPage extends Page\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder->add(\n            'myBlock',\n            GrapesJsBlockType::class,\n            [\n                'label' => 'My block',\n                'row_attr' => [\n                ],\n                'options' => [\n                    // options given to the sub form\n                ],\n            ]\n        );\n\n        // ...\n    }\n\n    public function setMyBlock(Block $block)\n    {\n        return $this->setBlock($block);\n    }\n\n    public function getMyBlock(): Block\n    {\n        return $this->getBlock('myBlock');\n    }\n\n    // ...\n}\n
"},{"location":"utils/editors/grapesjs/#options","title":"Options","text":"

There are 3 modes:

To specify a mode, you must define the attribute data-grapesjs:

src/Form/ExampleType.php
$builder->add(\n    'myField',\n    GrapesJsType::class,\n    [\n        // ...\n\n        'attr' => [\n            'data-grapesjs' => 'bootstrap4',\n        ],\n    ]\n);\n\n```php-inline title=\"src/Entity/Page/YourPage.php\"\n$builder->add(\n    'myBlock',\n    GrapesJsBlockType::class,\n    [\n        // ...\n\n        'options' => [\n            'attr' => [\n                'data-grapesjs' => 'bootstrap4',\n            ],\n        ],\n    ]\n);\n
"},{"location":"utils/editors/grapesjs/#rendering","title":"Rendering","text":"

GrapesJS will generate a JSON which contains HTML and CSS.

Depending of the mode, you will need to import in the app sass file:

"},{"location":"utils/editors/tinymce/","title":"TinyMCE","text":"

TinyMCE gives you total control over your rich text editing. It's fully integrated in Murph as form types.

"},{"location":"utils/editors/tinymce/#classic-form","title":"Classic form","text":"src/Form/ExampleType.php
namespace App\\Form\\ExampleType;\n\nuse App\\Core\\Form\\Type\\TinymceTextareaType;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\nclass ExampleType extends AbstractType\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder->add(\n            'myField',\n            TinymceTextareaType::class\n        );\n\n        // ...\n    }\n\n    // ...\n}\n
"},{"location":"utils/editors/tinymce/#page-form","title":"Page form","text":"src/Entity/Page/YourPage.php
namespace App\\Entity\\Page;\n\nuse App\\Core\\Entity\\Site\\Page\\Block;\nuse App\\Core\\Form\\Site\\Page\\TinymceTextareaBlockType;\n\n#[ORM\\Entity]\nclass YourPage extends Page\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder->add(\n            'myBlock',\n            TinymceTextareaBlockType::class,\n            [\n                'label' => 'My block',\n                'row_attr' => [\n                ],\n                'options' => [\n                    // options given to the sub form\n                ],\n            ]\n        );\n\n        // ...\n    }\n\n    public function setMyBlock(Block $block)\n    {\n        return $this->setBlock($block);\n    }\n\n    public function getMyBlock(): Block\n    {\n        return $this->getBlock('myBlock');\n    }\n\n    // ...\n}\n
"},{"location":"utils/editors/tinymce/#options","title":"Options","text":"

There are 2 predefined modes:

To specify a mode, you must define the attribute data-tinymce:

src/Form/ExampleType.php
$builder->add(\n    'myField',\n    TinymceTextareaType::class,\n    [\n        // ...\n\n        'attr' => [\n            'data-tinymce' => 'light',\n        ],\n    ]\n);\n
src/Entity/Page/YourPage.php
$builder->add(\n    'myBlock',\n    TinymceTextareaBlockType::class,\n    [\n        // ...\n\n        'options' => [\n            'attr' => [\n                'data-tinymce' => 'light',\n            ],\n        ],\n    ]\n);\n

To custom the editor, see the example below:

assets/js/admin.js
import '../../vendor/murph/murph-core/src/core/Resources/assets/js/admin.js'\n\nwindow.tinymce.language = 'fr_FR'\n\nwindow.tinymceModes = {\n  myCustomMode: {\n    plugins: '...',\n    menubar: '...',\n    toolbar: '...'\n    quickbars_selection_toolbar: '...'\n    contextmenu: '...'\n    templates: [\n      {\n        title: 'Container',\n        description: 'Add a bootstrap container',\n        content: '<div class=\"container\"><div class=\"selcontent\"></div></div>'\n      }\n      // ...\n    ],\n    content_style: '...'\n  }\n}\n
src/Form/ExampleType.php
$builder->add(\n    'myField',\n    TinymceTextareaType::class,\n    [\n        // ...\n\n        'attr' => [\n            'data-tinymce' => 'myCustomMode',\n        ],\n    ]\n);\n
src/Entity/Page/YourPage.php
$builder->add(\n    'myBlock',\n    TinymceTextareaBlockType::class,\n    [\n        // ...\n\n        'options' => [\n            'attr' => [\n                'data-tinymce' => 'myCustomMode',\n            ],\n        ],\n    ]\n);\n
"},{"location":"utils/editors/tinymce/#rendering","title":"Rendering","text":"

TinyMCE generates HTML. To render, simply use {{ value|raw }}.

"},{"location":"utils/form/collection/","title":"Collection","text":"

When you need to manage a collection in a form, you can use App\\Core\\Form\\Type\\CollectionType with these options:

Option Type Default Description collection_name string null Unique name of the collection label_add string Add Unique name of the collection label_delete string Delete Unique name of the collection template_before_item string null A template included before an existing itam of the collection template_after_item string null A template included after an existing itam of the collection

...and and all options of a symfony collection type.

"},{"location":"utils/form/file_picker/","title":"File picker","text":"

Murph provides a file picker using the file manager system: App\\Core\\Form\\FileManager\\FilePickerType. It allows you to pick an existing file or upload it. After saving, the file is previewed if possible.

"}]}