├── .circleci └── config.yml ├── .gitignore ├── Command ├── ExportCommand.php └── ImportCommand.php ├── Controller └── ParameterController.php ├── DependencyInjection ├── Compiler │ └── FieldTypePass.php ├── Configuration.php └── SherlockodeConfigurationExtension.php ├── Event ├── PostSaveEvent.php ├── PreSaveEvent.php ├── RequestEventTrait.php ├── ResponseEventTrait.php └── SaveEvent.php ├── FieldType ├── AbstractField.php ├── CheckboxField.php ├── ChoiceField.php ├── DateTimeField.php ├── EntityField.php ├── FieldTypeInterface.php ├── ImageField.php ├── PasswordField.php └── SimpleField.php ├── Form └── Type │ ├── ImageType.php │ ├── ImportType.php │ └── ParametersType.php ├── LICENSE ├── Manager ├── ConfigurationManager.php ├── ConfigurationManagerInterface.php ├── ConstraintManager.php ├── ConstraintManagerInterface.php ├── ExportManager.php ├── ExportManagerInterface.php ├── FieldTypeManager.php ├── FieldTypeManagerInterface.php ├── ImportManager.php ├── ImportManagerInterface.php ├── ParameterConverter.php ├── ParameterConverterInterface.php ├── ParameterManager.php ├── ParameterManagerInterface.php ├── UploadManager.php └── UploadManagerInterface.php ├── Model ├── Parameter.php └── ParameterInterface.php ├── Parameter └── ParameterDefinition.php ├── README.md ├── Resources ├── config │ ├── field_types.xml │ ├── routing.xml │ └── services.xml ├── translations │ ├── SherlockodeConfigurationBundle.en.yml │ └── SherlockodeConfigurationBundle.fr.yml └── views │ ├── base.html.twig │ ├── form.html.twig │ ├── import.html.twig │ └── parameters.html.twig ├── SherlockodeConfigurationBundle.php ├── Tests ├── ConfigurationManagerTest.php ├── FieldTypeManagerTest.php └── Transformer │ ├── ArrayTransformerTest.php │ ├── BooleanTransformerTest.php │ └── CallbackTransformerTest.php ├── Transformer ├── ArrayTransformer.php ├── BooleanTransformer.php ├── CallbackTransformer.php └── TransformerInterface.php ├── Twig └── ParameterExtension.php ├── composer.json └── phpunit.xml.dist /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | common: &common 4 | environment: 5 | SYMFONY_DEPRECATIONS_HELPER: disabled 6 | APP_ENV: test 7 | steps: 8 | - checkout 9 | - run: composer install --no-scripts 10 | - run: php ./vendor/bin/phpunit 11 | 12 | 13 | jobs: 14 | test80: 15 | docker: 16 | - image: cimg/php:8.0 17 | <<: *common 18 | test81: 19 | docker: 20 | - image: cimg/php:8.1 21 | <<: *common 22 | workflows: 23 | version: 2 24 | build: 25 | jobs: 26 | - test80 27 | - test81 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | phpunit.xml 4 | -------------------------------------------------------------------------------- /Command/ExportCommand.php: -------------------------------------------------------------------------------- 1 | exportManager = $exportManager; 25 | } 26 | 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $io = new SymfonyStyle($input, $output); 30 | $io->info('Start exporting configuration...'); 31 | $this->exportManager->exportInVault(); 32 | $io->success('Configuration successfully sealed in vault'); 33 | 34 | return Command::SUCCESS; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Command/ImportCommand.php: -------------------------------------------------------------------------------- 1 | importManager = $importManager; 25 | } 26 | 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $io = new SymfonyStyle($input, $output); 30 | $io->info('Start importing configuration...'); 31 | $this->importManager->importFromVault(); 32 | $io->success('Configuration successfully imported from vault'); 33 | 34 | return Command::SUCCESS; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Controller/ParameterController.php: -------------------------------------------------------------------------------- 1 | parameterManager = $parameterManager; 71 | $this->exportManager = $exportManager; 72 | $this->importManager = $importManager; 73 | $this->eventDispatcher = $eventDispatcher; 74 | $this->requestStack = $requestStack; 75 | $this->formFactory = $formFactory; 76 | $this->urlGenerator = $urlGenerator; 77 | $this->twig = $twig; 78 | $this->editFormTemplate = $editFormTemplate; 79 | $this->importFormTemplate = $importFormTemplate; 80 | $this->redirectAfterImportRoute = $redirectAfterImportRoute; 81 | } 82 | 83 | /** 84 | * @throws LoaderError 85 | * @throws RuntimeError 86 | * @throws SyntaxError 87 | */ 88 | public function listAction(Request $request): Response 89 | { 90 | $form = $this->formFactory->create(ParametersType::class, $this->parameterManager->getAll()); 91 | $form->handleRequest($request); 92 | 93 | if ($form->isSubmitted() && $form->isValid()) { 94 | $params = $form->getData(); 95 | 96 | $preSaveEvent = new PreSaveEvent($request, $params); 97 | $this->eventDispatcher->dispatch($preSaveEvent); 98 | 99 | if ($preSaveEvent->getResponse()) { 100 | return $preSaveEvent->getResponse(); 101 | } 102 | 103 | foreach ($params as $path => $value) { 104 | $this->parameterManager->set($path, $value); 105 | } 106 | $this->parameterManager->save(); 107 | 108 | $this->eventDispatcher->dispatch(new SaveEvent()); 109 | 110 | $postSaveEvent = new PostSaveEvent($request); 111 | $this->eventDispatcher->dispatch($postSaveEvent); 112 | 113 | if ($postSaveEvent->getResponse()) { 114 | return $postSaveEvent->getResponse(); 115 | } 116 | 117 | return new RedirectResponse($this->urlGenerator->generate('sherlockode_configuration.parameters')); 118 | } 119 | 120 | return new Response($this->twig->render($this->editFormTemplate, [ 121 | 'form' => $form->createView(), 122 | ])); 123 | } 124 | 125 | public function exportAction(): Response 126 | { 127 | $response = new Response($this->exportManager->exportAsString()); 128 | $disposition = HeaderUtils::makeDisposition( 129 | HeaderUtils::DISPOSITION_ATTACHMENT, 130 | 'parameters.yaml' 131 | ); 132 | $response->headers->set('Content-Disposition', $disposition); 133 | 134 | return $response; 135 | } 136 | 137 | /** 138 | * @throws LoaderError 139 | * @throws RuntimeError 140 | * @throws SyntaxError 141 | */ 142 | public function importAction(Request $request): Response 143 | { 144 | $form = $this->formFactory->create(ImportType::class, [], [ 145 | 'action' => $this->urlGenerator->generate('sherlockode_configuration.import'), 146 | 'method' => 'POST', 147 | ]); 148 | $form->handleRequest($this->requestStack->getMainRequest()); 149 | 150 | if ($form->isSubmitted() && $form->isValid()) { 151 | $session = $request->hasSession() ? $request->getSession() : null; 152 | 153 | try { 154 | $this->importManager->import($form->get('file')->getData()); 155 | 156 | if ($session) { 157 | $session->getFlashbag()->add('success', 'successfully_imported'); 158 | } 159 | } catch (\Exception $exception) { 160 | if ($session) { 161 | $session->getFlashbag()->add('error', 'an_error_occurred_during_import'); 162 | } 163 | } 164 | 165 | return new RedirectResponse($this->urlGenerator->generate($this->redirectAfterImportRoute)); 166 | } 167 | 168 | return new Response($this->twig->render($this->importFormTemplate, ['form' => $form->createView()])); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/FieldTypePass.php: -------------------------------------------------------------------------------- 1 | removeDefinition('sherlockode_configuration.field.entity'); 15 | } 16 | 17 | $definition = $container->findDefinition('sherlockode_configuration.field_manager'); 18 | $taggedServices = $container->findTaggedServiceIds('sherlockode_configuration.field'); 19 | 20 | foreach ($taggedServices as $id => $tags) { 21 | $definition->addMethodCall('addFieldType', [new Reference($id)]); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | getRootNode(); 14 | 15 | $rootNode 16 | ->fixXmlConfig('parameter') 17 | ->children() 18 | ->scalarNode('translation_domain')->defaultFalse()->end() 19 | ->arrayNode('entity_class') 20 | ->isRequired() 21 | ->children() 22 | ->scalarNode('parameter')->isRequired()->end() 23 | ->end() 24 | ->end() 25 | ->arrayNode('templates') 26 | ->addDefaultsIfNotSet() 27 | ->children() 28 | ->scalarNode('edit_form') 29 | ->cannotBeEmpty() 30 | ->defaultValue('@SherlockodeConfiguration/parameters.html.twig') 31 | ->end() 32 | ->scalarNode('import_form') 33 | ->cannotBeEmpty() 34 | ->defaultValue('@SherlockodeConfiguration/import.html.twig') 35 | ->end() 36 | ->end() 37 | ->end() 38 | ->arrayNode('parameters') 39 | ->useAttributeAsKey('path') 40 | ->arrayPrototype() 41 | ->children() 42 | ->scalarNode('label')->isRequired()->end() 43 | ->scalarNode('type')->isRequired()->end() 44 | ->scalarNode('default_value')->defaultNull()->end() 45 | ->scalarNode('translation_domain')->defaultNull()->end() 46 | ->variableNode('options')->end() 47 | ->end() 48 | ->end() 49 | ->end() 50 | ->arrayNode('upload') 51 | ->children() 52 | ->scalarNode('directory') 53 | ->cannotBeEmpty() 54 | ->end() 55 | ->scalarNode('uri_prefix') 56 | ->cannotBeEmpty() 57 | ->end() 58 | ->end() 59 | ->end() 60 | ->arrayNode('import') 61 | ->addDefaultsIfNotSet() 62 | ->children() 63 | ->scalarNode('redirect_after') 64 | ->defaultValue('sherlockode_configuration.parameters') 65 | ->cannotBeEmpty() 66 | ->end() 67 | ->end() 68 | ->end() 69 | ->end() 70 | ; 71 | 72 | return $treeBuilder; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /DependencyInjection/SherlockodeConfigurationExtension.php: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 17 | 18 | $container->setParameter('sherlockode_configuration.parameter_class', $config['entity_class']['parameter']); 19 | $container->setParameter('sherlockode_configuration.parameters', $config['parameters']); 20 | $container->setParameter('sherlockode_configuration.translation_domain', $config['translation_domain']); 21 | $container->setParameter('sherlockode_configuration.templates.edit_form', $config['templates']['edit_form']); 22 | $container->setParameter('sherlockode_configuration.templates.import_form', $config['templates']['import_form']); 23 | $container->setParameter('sherlockode_configuration.import.redirect_after', $config['import']['redirect_after']); 24 | 25 | $fileLocator = new FileLocator(__DIR__ . '/../Resources/config'); 26 | $loader = new XmlFileLoader($container, $fileLocator); 27 | $loader->load('services.xml'); 28 | $loader->load('field_types.xml'); 29 | 30 | $container->registerForAutoconfiguration(FieldTypeInterface::class) 31 | ->addTag('sherlockode_configuration.field'); 32 | 33 | $targetDir = $config['upload']['directory'] ?? sys_get_temp_dir(); 34 | $webPath = $config['upload']['uri_prefix'] ?? '/'; 35 | 36 | $uploadManager = $container->getDefinition('sherlockode_configuration.upload_manager'); 37 | if ($uploadManager->getClass() == 'Sherlockode\\ConfigurationBundle\\Manager\\UploadManager') { 38 | $uploadManager->setArguments([ 39 | $targetDir, 40 | $webPath, 41 | ]); 42 | } 43 | 44 | $this->registerFormTheme($container); 45 | } 46 | 47 | private function registerFormTheme(ContainerBuilder $container): void 48 | { 49 | $resources = $container->hasParameter('twig.form.resources') ? 50 | $container->getParameter('twig.form.resources') : []; 51 | 52 | \array_unshift($resources, '@SherlockodeConfiguration/form.html.twig'); 53 | $container->setParameter('twig.form.resources', $resources); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Event/PostSaveEvent.php: -------------------------------------------------------------------------------- 1 | request = $request; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Event/PreSaveEvent.php: -------------------------------------------------------------------------------- 1 | request = $request; 17 | $this->parameters = $parameters; 18 | } 19 | 20 | public function getParameters(): array 21 | { 22 | return $this->parameters; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Event/RequestEventTrait.php: -------------------------------------------------------------------------------- 1 | request; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Event/ResponseEventTrait.php: -------------------------------------------------------------------------------- 1 | response; 14 | } 15 | 16 | public function setResponse(?Response $response): self 17 | { 18 | $this->response = $response; 19 | 20 | return $this; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Event/SaveEvent.php: -------------------------------------------------------------------------------- 1 | $definition->getOption('required', false), 21 | ]; 22 | } 23 | 24 | public function getName(): string 25 | { 26 | return 'checkbox'; 27 | } 28 | 29 | public function getModelTransformer(ParameterDefinition $definition): ?TransformerInterface 30 | { 31 | return new BooleanTransformer(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /FieldType/ChoiceField.php: -------------------------------------------------------------------------------- 1 | $definition->getOption('choices', []), 21 | 'multiple' => $definition->getOption('multiple', false), 22 | 'expanded' => $definition->getOption('expanded', false), 23 | 'placeholder' => $definition->getOption('placeholder', null), 24 | ]; 25 | } 26 | 27 | public function getName(): string 28 | { 29 | return 'choice'; 30 | } 31 | 32 | public function getModelTransformer(ParameterDefinition $definition): ?TransformerInterface 33 | { 34 | if ($definition->getOption('multiple', false)) { 35 | return new ArrayTransformer(); 36 | } 37 | 38 | return null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FieldType/DateTimeField.php: -------------------------------------------------------------------------------- 1 | format(\DateTime::ATOM); 35 | } 36 | 37 | return ''; 38 | } 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /FieldType/EntityField.php: -------------------------------------------------------------------------------- 1 | em = $em; 18 | } 19 | 20 | public function getFormType(ParameterDefinition $definition): string 21 | { 22 | return EntityType::class; 23 | } 24 | 25 | public function getFormOptions(ParameterDefinition $definition): array 26 | { 27 | $options = [ 28 | 'class' => $definition->getOption('class'), 29 | 'placeholder' => $definition->getOption('placeholder', null), 30 | 'multiple' => $definition->getOption('multiple', null), 31 | ]; 32 | 33 | $label = $definition->getOption('choice_label', null); 34 | // only set the choice_label if defined to keep the Form native behavior 35 | if ($label) { 36 | $options['choice_label'] = $label; 37 | } 38 | 39 | return $options; 40 | } 41 | 42 | public function getName(): string 43 | { 44 | return 'entity'; 45 | } 46 | 47 | public function getModelTransformer(ParameterDefinition $definition): ?TransformerInterface 48 | { 49 | $class = $definition->getOption('class'); 50 | $multiple = $definition->getOption('multiple'); 51 | 52 | return new CallbackTransformer(function ($data) use ($class, $multiple) { 53 | if (!$data) { 54 | return null; 55 | } 56 | 57 | if ($multiple) { 58 | $data = explode(',', $data); 59 | $metadata = $this->em->getClassMetadata($class); 60 | $idField = $metadata->getSingleIdentifierFieldName(); 61 | $result = $this->em->getRepository($class)->findBy([$idField => $data]); 62 | } else { 63 | $result = $this->em->getRepository($class)->find($data); 64 | } 65 | 66 | return $result; 67 | }, function ($data) use ($class, $multiple) { 68 | if (!$data) { 69 | return null; 70 | } 71 | $metadata = $this->em->getClassMetadata($class); 72 | 73 | if ($multiple) { 74 | $result = []; 75 | foreach ($data as $entity) { 76 | $id = $metadata->getIdentifierValues($entity); 77 | $result[] = reset($id); 78 | } 79 | $result = implode(',', $result); 80 | } else { 81 | $id = $metadata->getIdentifierValues($data); 82 | $result = reset($id); 83 | } 84 | 85 | return $result; 86 | }); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /FieldType/FieldTypeInterface.php: -------------------------------------------------------------------------------- 1 | em = $em; 20 | $this->parameterClass = $parameterClass; 21 | } 22 | 23 | public function getFormType(ParameterDefinition $definition): string 24 | { 25 | return PasswordType::class; 26 | } 27 | 28 | public function getName(): string 29 | { 30 | return 'password'; 31 | } 32 | 33 | public function getModelTransformer(ParameterDefinition $definition): ?TransformerInterface 34 | { 35 | $parameter = $this->em->getRepository($this->parameterClass)->findOneBy(['path' => $definition->getPath()]); 36 | $currentValue = $parameter ? $parameter->getValue() : null; 37 | 38 | return new CallbackTransformer(function ($data) { 39 | return $data; 40 | }, function ($data) use ($currentValue) { 41 | if (!$data && $currentValue) { 42 | return $currentValue; 43 | } 44 | 45 | return $data; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /FieldType/SimpleField.php: -------------------------------------------------------------------------------- 1 | getOption('subtype', TextType::class); 13 | 14 | if (!class_exists($formType)) { 15 | throw new \RuntimeException(sprintf('Undefined form type %s', $formType)); 16 | } 17 | 18 | return $formType; 19 | } 20 | 21 | public function getName(): string 22 | { 23 | return 'simple'; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Form/Type/ImageType.php: -------------------------------------------------------------------------------- 1 | uploadManager = $uploadManager; 24 | } 25 | 26 | public function buildForm(FormBuilderInterface $builder, array $options): void 27 | { 28 | $builder 29 | ->add('file', FileType::class, [ 30 | 'label' => 'field_type.image.file', 31 | 'required' => false, 32 | 'translation_domain' => 'SherlockodeConfigurationBundle', 33 | ]) 34 | ->add('delete', CheckboxType::class, [ 35 | 'required' => false, 36 | 'label' => 'field_type.image.delete', 37 | 'translation_domain' => 'SherlockodeConfigurationBundle', 38 | ]) 39 | ; 40 | 41 | $filename = ''; 42 | $builder->addEventListener( 43 | FormEvents::PRE_SET_DATA, 44 | function (FormEvent $event) use (&$filename) { 45 | $data = $event->getData(); 46 | if (isset($data['src'])) { 47 | $filename = $data['src']; 48 | } 49 | } 50 | ); 51 | 52 | $builder->addEventListener( 53 | FormEvents::SUBMIT, 54 | function (FormEvent $event) use ($options, &$filename) { 55 | $data = $event->getData(); 56 | if ($data['delete']) { 57 | $data['src'] = ''; 58 | $this->uploadManager->remove($filename); 59 | } 60 | if ($data['file'] instanceof UploadedFile) { 61 | if ($filename !== '') { 62 | $this->uploadManager->remove($filename); 63 | } 64 | $data['src'] = $this->uploadManager->upload($data['file']); 65 | } 66 | unset($data['file']); 67 | unset($data['delete']); 68 | $event->setData($data); 69 | } 70 | ); 71 | } 72 | 73 | public function configureOptions(OptionsResolver $resolver): void 74 | { 75 | $resolver->setDefault('translation_domain', 'SherlockodeConfigurationBundle'); 76 | } 77 | 78 | /** 79 | * {@inheritDoc} 80 | */ 81 | public function buildView(FormView $view, FormInterface $form, array $options): void 82 | { 83 | $source = ''; 84 | $data = $form->getData(); 85 | if (!empty($data['src'])) { 86 | $source = $this->uploadManager->resolveUri($data['src']); 87 | } 88 | 89 | $view->vars['source'] = $source; 90 | 91 | } 92 | 93 | public function getBlockPrefix(): string 94 | { 95 | return 'config_image'; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Form/Type/ImportType.php: -------------------------------------------------------------------------------- 1 | add('file', FileType::class, [ 17 | 'label' => 'import_file', 18 | 'translation_domain' => 'SherlockodeConfigurationBundle', 19 | 'constraints' => [new NotBlank(), new File(['mimeTypes' => ['text/plain']])], 20 | ]) 21 | ; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Form/Type/ParametersType.php: -------------------------------------------------------------------------------- 1 | fieldTypeManager = $fieldTypeManager; 28 | $this->configurationManager = $configurationManager; 29 | $this->constraintManager = $constraintManager; 30 | } 31 | 32 | public function buildForm(FormBuilderInterface $builder, array $options): void 33 | { 34 | foreach ($this->configurationManager->getDefinedParameters() as $definition) { 35 | $field = $this->fieldTypeManager->getField($definition->getType()); 36 | 37 | $baseOptions = [ 38 | 'label' => $definition->getLabel(), 39 | 'required' => $definition->getOption('required', true), 40 | 'attr' => $definition->getOption('attr', []), 41 | 'row_attr' => $definition->getOption('row_attr', []), 42 | 'label_attr' => $definition->getOption('label_attr', []), 43 | 'help' => $definition->getOption('help'), 44 | 'translation_domain' => $definition->getTranslationDomain(), 45 | 'constraints' => $this->constraintManager->getConstraints($definition->getOption('constraints', [])), 46 | ]; 47 | $childOptions = array_merge($baseOptions, $field->getFormOptions($definition)); 48 | 49 | $builder 50 | ->add($definition->getPath(), $field->getFormType($definition), $childOptions) 51 | ; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2023 Sherlockode 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Manager/ConfigurationManager.php: -------------------------------------------------------------------------------- 1 | definitions = $this->processConfiguration($config, $translationDomain); 17 | } 18 | 19 | /** 20 | * Get the list of parameters in the configuration 21 | * 22 | * @return ParameterDefinition[] 23 | */ 24 | public function getDefinedParameters(): array 25 | { 26 | return $this->definitions; 27 | } 28 | 29 | /** 30 | * @throws \Exception 31 | */ 32 | public function get(string $path): ParameterDefinition 33 | { 34 | if (!isset($this->definitions[$path])) { 35 | throw new \Exception(sprintf('Undefined parameter path "%s"', $path)); 36 | } 37 | 38 | return $this->definitions[$path]; 39 | } 40 | 41 | public function has(string $path): bool 42 | { 43 | return isset($this->definitions[$path]); 44 | } 45 | 46 | /** 47 | * @return ParameterDefinition[] 48 | */ 49 | private function processConfiguration(array $config, string|false $translationDomain = false): array 50 | { 51 | $result = []; 52 | foreach ($config as $path => $data) { 53 | if (!isset($data['type'])) { 54 | throw new \LogicException(sprintf('No type has been set for parameter "%s"', $path)); 55 | } 56 | $definition = new ParameterDefinition($path, $data['type']); 57 | if (isset($data['label'])) { 58 | $definition->setLabel($data['label']); 59 | } 60 | if (!isset($data['translation_domain'])) { 61 | $data['translation_domain'] = $translationDomain; 62 | } 63 | if (isset($data['default_value'])) { 64 | $definition->setDefaultValue($data['default_value']); 65 | } 66 | $definition->setTranslationDomain($data['translation_domain']); 67 | if (isset($data['options']) && is_array($data['options'])) { 68 | $definition->setOptions($data['options']); 69 | } 70 | $result[$path] = $definition; 71 | } 72 | 73 | return $result; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Manager/ConfigurationManagerInterface.php: -------------------------------------------------------------------------------- 1 | $options) { 16 | $constraints[] = $this->create($class, $options); 17 | } 18 | } 19 | 20 | return $constraints; 21 | } 22 | 23 | /** 24 | * @throws \Exception 25 | */ 26 | private function create(string $fqcn, ?array $args): Constraint 27 | { 28 | if (!class_exists($fqcn)) { 29 | $fqcn = sprintf('%s%s', AbstractLoader::DEFAULT_NAMESPACE, $fqcn); 30 | } 31 | 32 | if (!is_a($fqcn, Constraint::class, true)) { 33 | throw new \Exception(sprintf('"%s" is not a valid Constraint class.', $fqcn)); 34 | } 35 | 36 | if (is_iterable($args)) { 37 | return new $fqcn(...$args); 38 | } 39 | 40 | return new $fqcn(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Manager/ConstraintManagerInterface.php: -------------------------------------------------------------------------------- 1 | parameterManager = $parameterManager; 19 | $this->parameterConverter = $parameterConverter; 20 | $this->vault = $vault; 21 | } 22 | 23 | public function exportAsString(): string 24 | { 25 | $parameters = []; 26 | foreach ($this->parameterManager->getAll() as $path => $value) { 27 | $stringValue = $this->parameterConverter->getStringValue($path, $value); 28 | 29 | if (null !== $stringValue) { 30 | $parameters[$path] = $stringValue; 31 | } 32 | } 33 | 34 | return Yaml::dump($parameters); 35 | } 36 | 37 | /** 38 | * @throws \Exception 39 | */ 40 | public function exportInVault(): void 41 | { 42 | foreach ($this->parameterManager->getAll() as $path => $value) { 43 | $stringValue = $this->parameterConverter->getStringValue($path, $value); 44 | 45 | if (null !== $stringValue) { 46 | $this->vault->seal($path, $stringValue); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Manager/ExportManagerInterface.php: -------------------------------------------------------------------------------- 1 | fieldTypes = []; 17 | } 18 | 19 | public function addFieldType(FieldTypeInterface $fieldType): self 20 | { 21 | $this->fieldTypes[$fieldType->getName()] = $fieldType; 22 | 23 | return $this; 24 | } 25 | 26 | /** 27 | * @throws \Exception 28 | */ 29 | public function getField(string $type): FieldTypeInterface 30 | { 31 | if (!isset($this->fieldTypes[$type])) { 32 | throw new \Exception(sprintf('Unknown parameter type "%s"', $type)); 33 | } 34 | 35 | return $this->fieldTypes[$type]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Manager/FieldTypeManagerInterface.php: -------------------------------------------------------------------------------- 1 | parameterManager = $parameterManager; 22 | $this->parameterConverter = $parameterConverter; 23 | $this->configurationManager = $configurationManager; 24 | $this->vault = $vault; 25 | } 26 | 27 | public function import(File $source): void 28 | { 29 | $raw = Yaml::parseFile($source->getRealPath()); 30 | 31 | foreach ($raw as $path => $stringValue) { 32 | if ($this->configurationManager->has($path)) { 33 | $this->parameterManager->set($path, $this->parameterConverter->getUserValue($path, $stringValue)); 34 | } 35 | } 36 | 37 | $this->parameterManager->save(); 38 | unlink($source->getRealPath()); 39 | } 40 | 41 | public function importFromVault(): void 42 | { 43 | foreach ($this->configurationManager->getDefinedParameters() as $definition) { 44 | $stringValue = $this->vault->reveal($definition->getPath()); 45 | 46 | if (null !== $stringValue) { 47 | $this->parameterManager->set($definition->getPath(), $this->parameterConverter->getUserValue($definition->getPath(), $stringValue)); 48 | } 49 | } 50 | 51 | $this->parameterManager->save(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Manager/ImportManagerInterface.php: -------------------------------------------------------------------------------- 1 | configurationManager->get($path); 16 | $fieldType = $this->fieldTypeManager->getField($parameterConfig->getType()); 17 | 18 | if ($transformer = $fieldType->getModelTransformer($parameterConfig)) { 19 | $value = $transformer->reverseTransform($value); 20 | } 21 | 22 | return $value; 23 | } 24 | 25 | public function getUserValue(string $path, ?string $value): mixed 26 | { 27 | $parameterDefinition = $this->configurationManager->get($path); 28 | $fieldType = $this->fieldTypeManager->getField($parameterDefinition->getType()); 29 | 30 | if ($transformer = $fieldType->getModelTransformer($parameterDefinition)) { 31 | $value = $transformer->transform($value); 32 | } 33 | 34 | return $value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Manager/ParameterConverterInterface.php: -------------------------------------------------------------------------------- 1 | value 38 | */ 39 | private array $data; 40 | 41 | private bool $loaded; 42 | 43 | private ConfigurationManagerInterface $configurationManager; 44 | 45 | private ParameterConverterInterface $parameterConverter; 46 | 47 | /** 48 | * ParameterManager constructor. 49 | */ 50 | public function __construct( 51 | EntityManagerInterface $em, 52 | string $class, 53 | ConfigurationManagerInterface $configurationManager, 54 | ParameterConverterInterface $parameterConverter 55 | ) { 56 | $this->em = $em; 57 | $this->class = $class; 58 | $this->configurationManager = $configurationManager; 59 | $this->parameters = []; 60 | $this->newParameters = []; 61 | $this->data = []; 62 | $this->loaded = false; 63 | $this->parameterConverter = $parameterConverter; 64 | } 65 | 66 | public function getClass(): string 67 | { 68 | return $this->class; 69 | } 70 | 71 | public function getAll(): array 72 | { 73 | if (false === $this->loaded) { 74 | $this->loadParameters(); 75 | } 76 | 77 | foreach ($this->configurationManager->getDefinedParameters() as $path => $parameterDefinition) { 78 | if (!isset($this->data[$path])) { 79 | // check if the value exists in the DB 80 | if (isset($this->parameters[$path])) { 81 | $value = $this->parameters[$path]->getValue(); 82 | } else { 83 | $value = $parameterDefinition->getDefaultValue(); 84 | } 85 | 86 | // check that every parameter has been transformed to its user value 87 | if (!is_null($value)) { 88 | $this->data[$path] = $this->parameterConverter->getUserValue($path, $value); 89 | } 90 | } 91 | } 92 | 93 | return $this->data; 94 | } 95 | 96 | public function get(string $path, mixed $default = null): mixed 97 | { 98 | if (false === $this->loaded) { 99 | $this->loadParameters(); 100 | } 101 | 102 | if (isset($this->data[$path])) { 103 | return $this->data[$path]; 104 | } 105 | 106 | $value = null; 107 | if (isset($this->parameters[$path])) { 108 | $value = $this->parameters[$path]->getValue(); 109 | } else { 110 | $value = $this->configurationManager->get($path)->getDefaultValue(); 111 | } 112 | 113 | // transform to user value only when the value is requested 114 | if (!is_null($value)) { 115 | $this->data[$path] = $this->parameterConverter->getUserValue($path, $value); 116 | return $this->data[$path]; 117 | } 118 | 119 | return $value ?? $default; 120 | } 121 | 122 | public function set(string $path, mixed $value): self 123 | { 124 | if (false === $this->loaded) { 125 | $this->loadParameters(); 126 | } 127 | if (!$this->configurationManager->has($path)) { 128 | return $this; 129 | } 130 | if (!isset($this->parameters[$path])) { 131 | /** @var ParameterInterface $parameter */ 132 | $parameter = new $this->class(); 133 | $parameter->setPath($path); 134 | $this->parameters[$path] = $parameter; 135 | $this->newParameters[] = $parameter; 136 | } 137 | $stringValue = $this->parameterConverter->getStringValue($path, $value); 138 | $this->parameters[$path]->setValue($stringValue); 139 | $this->data[$path] = $this->parameterConverter->getUserValue($path, $stringValue); 140 | 141 | return $this; 142 | } 143 | 144 | /** 145 | * Save all parameters into the database 146 | */ 147 | public function save(): void 148 | { 149 | foreach ($this->newParameters as $parameter) { 150 | $this->em->persist($parameter); 151 | } 152 | $this->em->flush($this->parameters); 153 | } 154 | 155 | /** 156 | * Load the parameters from the database 157 | * and transform their string value into the expected type 158 | */ 159 | private function loadParameters(): void 160 | { 161 | $parameters = $this->em->getRepository($this->class)->findAll(); 162 | /** @var ParameterInterface $parameter */ 163 | foreach ($parameters as $parameter) { 164 | if (!$this->configurationManager->has($parameter->getPath())) { 165 | continue; 166 | } 167 | $this->parameters[$parameter->getPath()] = $parameter; 168 | } 169 | $this->loaded = true; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /Manager/ParameterManagerInterface.php: -------------------------------------------------------------------------------- 1 | value 11 | */ 12 | public function getAll(): array; 13 | 14 | /** 15 | * Save all parameters into the database 16 | */ 17 | public function save(): void; 18 | 19 | /** 20 | * Get an existing parameter value for a given path 21 | */ 22 | public function get(string $path, mixed $default = null): mixed; 23 | 24 | /** 25 | * Set the value of a parameter at given path 26 | */ 27 | public function set(string $path, mixed $value): self; 28 | } 29 | -------------------------------------------------------------------------------- /Manager/UploadManager.php: -------------------------------------------------------------------------------- 1 | targetDir = $targetDir; 16 | $this->webPath = $webPath; 17 | } 18 | 19 | /** 20 | * Upload file on server 21 | */ 22 | public function upload(?UploadedFile $file = null): string 23 | { 24 | if ($file === null) { 25 | return ''; 26 | } 27 | 28 | $filename = $this->generateFilename($file); 29 | $file->move($this->targetDir, $filename); 30 | 31 | return $filename; 32 | } 33 | 34 | /** 35 | * Remove file 36 | */ 37 | public function remove(string $filename): void 38 | { 39 | $filepath = $this->getFilePath($filename); 40 | 41 | if (!file_exists($filepath)) { 42 | return; 43 | } 44 | 45 | unlink($filepath); 46 | } 47 | 48 | public function getFilePath(string $filename): string 49 | { 50 | return $this->targetDir . DIRECTORY_SEPARATOR . $filename; 51 | } 52 | 53 | /** 54 | * Generate the filename to store on disk 55 | */ 56 | private function generateFilename(UploadedFile $file): string 57 | { 58 | $extension = $file->getClientOriginalExtension(); 59 | $filename = str_replace('.' . $extension, '', $file->getClientOriginalName()); 60 | 61 | $filename = $filename . '_' . md5(uniqid()) . '.' . $extension; 62 | 63 | return $filename; 64 | } 65 | 66 | public function resolveUri(string $filename): string 67 | { 68 | return $this->webPath . '/' . $filename; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Manager/UploadManagerInterface.php: -------------------------------------------------------------------------------- 1 | id; 16 | } 17 | 18 | public function getPath(): string 19 | { 20 | return $this->path; 21 | } 22 | 23 | public function setPath(string $path): self 24 | { 25 | $this->path = $path; 26 | 27 | return $this; 28 | } 29 | 30 | public function getValue(): ?string 31 | { 32 | return $this->value; 33 | } 34 | 35 | public function setValue(?string $value): self 36 | { 37 | $this->value = $value; 38 | 39 | return $this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Model/ParameterInterface.php: -------------------------------------------------------------------------------- 1 | path = $path; 25 | $this->type = $type; 26 | 27 | $this->label = $this->path; 28 | } 29 | 30 | public function getPath(): string 31 | { 32 | return $this->path; 33 | } 34 | 35 | public function getLabel(): string 36 | { 37 | return $this->label; 38 | } 39 | 40 | public function setLabel(string $label): self 41 | { 42 | $this->label = $label; 43 | 44 | return $this; 45 | } 46 | 47 | public function getType(): string 48 | { 49 | return $this->type; 50 | } 51 | 52 | public function getDefaultValue(): ?string 53 | { 54 | return $this->defaultValue; 55 | } 56 | 57 | public function setDefaultValue(?string $defaultValue): self 58 | { 59 | $this->defaultValue = $defaultValue; 60 | 61 | return $this; 62 | } 63 | 64 | public function getTranslationDomain(): string 65 | { 66 | return $this->translationDomain; 67 | } 68 | 69 | public function setTranslationDomain(string $translationDomain): self 70 | { 71 | $this->translationDomain = $translationDomain; 72 | 73 | return $this; 74 | } 75 | 76 | public function getOption(string $optionName, mixed $default = null): mixed 77 | { 78 | if (!isset($this->options[$optionName])) { 79 | return $default; 80 | } 81 | 82 | return $this->options[$optionName]; 83 | } 84 | 85 | public function getOptions(): array 86 | { 87 | return $this->options; 88 | } 89 | 90 | public function setOptions(array $options): self 91 | { 92 | $this->options = $options; 93 | 94 | return $this; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sherlockode ConfigurationBundle 2 | =============================== 3 | 4 | This bundle is built to allow users to define configuration options, useful in admin panels. 5 | Get rid of the business logic variables in your `ENV` or `parameters.yml` file ! 6 | 7 | Available features: 8 | 9 | * Define multiple configuration entries available for the users 10 | * Retrieve configuration values anywhere in your code for business logic 11 | * Use or create custom parameter types for a better user experience 12 | 13 | [![CircleCI](https://circleci.com/gh/sherlockode/configuration-bundle.svg?style=shield)](https://circleci.com/gh/sherlockode/configuration-bundle) 14 | [![Total Downloads](https://poser.pugx.org/sherlockode/configuration-bundle/downloads)](https://packagist.org/packages/sherlockode/configuration-bundle) 15 | [![Latest Stable Version](https://poser.pugx.org/sherlockode/configuration-bundle/v/stable)](https://packagist.org/packages/sherlockode/configuration-bundle) 16 | 17 | ## Installation 18 | 19 | The best way to install this bundle is to rely on [Composer](https://getcomposer.org/): 20 | 21 | ```bash 22 | $ composer require sherlockode/configuration-bundle 23 | ``` 24 | 25 | ## Setup 26 | 27 | Enable the bundle in the kernel 28 | 29 | ```php 30 | ['all' => true], 36 | ]; 37 | ``` 38 | 39 | You will need a `Parameter` entity in order to store the configuration values in the database. 40 | 41 | ```php 42 | getAll(); 204 | // or using an associative array: 205 | // $data = ['contact_email' => 'me@example.com', 'max_user_login_attempts' => 5]; 206 | 207 | $form = $this->createForm(ParametersType::class, $data); 208 | // handle form submission 209 | $form->handleRequest($request); 210 | if ($form->isSubmitted() && $form->isValid()) { 211 | $params = $form->getData(); 212 | foreach ($params as $path => $value) { 213 | $parameterManager->set($path, $value); 214 | } 215 | $parameterManager->save(); 216 | } 217 | //... 218 | ``` 219 | 220 | ### Access configuration values 221 | 222 | You are now able to retrieve any configuration value by using the `get` method from the parameter manager. 223 | It is possible to provide a default value to return if the entry has not been set yet. 224 | 225 | ```php 226 | $email = $parameterManager->get('contact_email'); 227 | $maxAttempts = $parameterManager->get('max_user_login_attempts', 5); 228 | ``` 229 | 230 | ### Import / export from the browser 231 | 232 | You can export or import parameters in a yaml file. You have two routes for these operations: 233 | 234 | * `sherlockode_configuration.export` 235 | * `sherlockode_configuration.import` 236 | 237 | You also can customize the import form template by defining your own in the configuration: 238 | 239 | ```yaml 240 | # config/packages/sherlockode_configuration.yaml 241 | sherlockode_configuration: 242 | templates: 243 | import_form: 'Parameter/my_import_form.html.twig' 244 | ``` 245 | 246 | ### Import / export from CLI 247 | 248 | You can export or import your parameters with these two Symfony commands: 249 | 250 | * `sherlockode:configuration:export` 251 | * `sherlockode:configuration:import` 252 | 253 | These commands rely on the [Symfony secrets mechanism](https://symfony.com/doc/current/configuration/secrets.html). 254 | So be sure to generate your credentials with the following command: 255 | 256 | ```shell 257 | $ php bin/console secrets:generate-keys 258 | ``` 259 | 260 | ### Events 261 | 262 | When parameters are saved in database, multiple events are dispatched 263 | 264 | * `Sherlockode\ConfigurationBundle\Event\PreSaveEvent` is dispatch before saving parameters; 265 | * `Sherlockode\ConfigurationBundle\Event\SaveEvent` is dispatch just after saving new parameters values 266 | in database; 267 | * `Sherlockode\ConfigurationBundle\Event\PostSaveEvent` is dispatch just after the SaveEvent. It can be 268 | useful for customizing redirection or adding flash message for example; 269 | 270 | ## Field types 271 | 272 | ### Default types 273 | 274 | Here are the field types provided in the bundle, located in the namespace `Sherlockode\ConfigurationBundle\FieldType` : 275 | 276 | * simple 277 | * checkbox 278 | * choice 279 | * datetime 280 | * entity 281 | * image 282 | * password 283 | 284 | ### Custom Field types 285 | 286 | In order to add custom field types, you should create a service implementing the `FieldTypeInterface` interface 287 | and tag it with `sherlockode_configuration.field` (or use autoconfiguration). 288 | 289 | The `getName()` return value is the alias of the field type to use in the configuration (like `simple` or `choice`). 290 | 291 | ### Using transformers 292 | 293 | Due to the format of the Parameter entity in the database (the value is stored as a string, whatever the parameter type), 294 | complex values cannot be stored directly. 295 | For instance, we can serialize an array to fit the string type, or we may store the ID of a database entity. 296 | The process may vary depending on your needs and the value to store, but the application needs to be aware of the process 297 | to transform the PHP data into a string and the opposite process. This is done through transformers. 298 | 299 | A transformer is an object implementing the `Sherlockode\ConfigurationBundle\Transformer\TransformerInterface`. 300 | The interface has two methods `transform` and `reverseTransform`, similarly to the transformers used by Symfony in the Form Component. 301 | 302 | The `transform` method takes the string representation and returns the PHP value, 303 | when the `reverseTransform` takes your PHP value and returns back the corresponding scalar value. 304 | 305 | In order to be used, an instance of the transformer should be returned by the `getModelTransformer` 306 | method of the corresponding field type. If this method returns `null`, the bundle considers that no transformation is needed. 307 | 308 | The bundle also provides a `CallbackTransformer` that can be used for faster implementations. 309 | For instance handling an array can be done like this : 310 | 311 | ```php 312 | public function getModelTransformer(ParameterDefinition $definition): ?TransformerInterface 313 | { 314 | return new CallbackTransformer( 315 | function ($data) { 316 | if (!$data) { 317 | return null; 318 | } 319 | if (false !== ($unserialized = @unserialize($data))) { 320 | return $unserialized; 321 | } 322 | return $data; 323 | }, 324 | function ($data) { 325 | if (is_array($data)) { 326 | return serialize($data); 327 | } 328 | return $data; 329 | } 330 | ); 331 | } 332 | ``` 333 | 334 | ## License 335 | 336 | This bundle is under the MIT license. Check the details in the [dedicated file](LICENSE) 337 | -------------------------------------------------------------------------------- /Resources/config/field_types.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | %sherlockode_configuration.parameter_class% 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Resources/config/routing.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sherlockode_configuration.controller.parameter::listAction 5 | 6 | 7 | sherlockode_configuration.controller.parameter::exportAction 8 | 9 | 10 | sherlockode_configuration.controller.parameter::importAction 11 | 12 | 13 | -------------------------------------------------------------------------------- /Resources/config/services.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | %sherlockode_configuration.templates.edit_form% 24 | %sherlockode_configuration.templates.import_form% 25 | %sherlockode_configuration.import.redirect_after% 26 | 27 | 28 | 29 | %sherlockode_configuration.parameters% 30 | %sherlockode_configuration.translation_domain% 31 | 32 | 33 | 34 | 35 | %sherlockode_configuration.parameter_class% 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /Resources/translations/SherlockodeConfigurationBundle.en.yml: -------------------------------------------------------------------------------- 1 | an_error_occurred_during_import: An error occurred during import 2 | import_file: Import a file 3 | parameters: Parameters 4 | submit: Submit 5 | successfully_imported: Your file has been successfully imported 6 | field_type: 7 | image: 8 | file: File 9 | delete: Delete 10 | -------------------------------------------------------------------------------- /Resources/translations/SherlockodeConfigurationBundle.fr.yml: -------------------------------------------------------------------------------- 1 | an_error_occurred_during_import: Une erreur est survenue durant l'import 2 | import_file: Importer un fichier 3 | parameters: Paramètres 4 | submit: Valider 5 | successfully_imported: Votre fichier a été importé avec succès 6 | field_type: 7 | image: 8 | file: Fichier 9 | delete: Supprimer 10 | -------------------------------------------------------------------------------- /Resources/views/base.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | {% block content %}{% endblock %} 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /Resources/views/form.html.twig: -------------------------------------------------------------------------------- 1 | {% block config_image_widget %} 2 | {{ form_row(form.file) }} 3 | 4 | {% if source != '' %} 5 |
6 | 7 | {{ form.vars.label|trans({}, form.vars.translation_domain) }} 8 | 9 |
10 | {% endif %} 11 | 12 |
13 | {{ form_row(form.delete) }} 14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /Resources/views/import.html.twig: -------------------------------------------------------------------------------- 1 | {{ form_start(form) }} 2 | {{ form_row(form.file) }} 3 | 4 | {{ form_end(form) }} 5 | -------------------------------------------------------------------------------- /Resources/views/parameters.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@SherlockodeConfiguration/base.html.twig' %} 2 | 3 | {% block content %} 4 |

{{ 'parameters'|trans({}, 'SherlockodeConfigurationBundle') }}

5 | {{ form_start(form) }} 6 | {% for child in form %} 7 | {{ form_row(child) }} 8 | {% endfor %} 9 | 10 | 11 | {{ form_end(form) }} 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /SherlockodeConfigurationBundle.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new FieldTypePass()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/ConfigurationManagerTest.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'type' => 'string', 16 | 'label' => 'test', 17 | 'default_value' => '42', 18 | 'options' => ['opt' => 'val'], 19 | 'translation_domain' => 'translation', 20 | ], 21 | ]; 22 | 23 | $configurationManager = new ConfigurationManager($config); 24 | $parameterDefinition = new ParameterDefinition('path', 'string'); 25 | $parameterDefinition->setLabel('test'); 26 | $parameterDefinition->setOptions(['opt' => 'val']); 27 | $parameterDefinition->setTranslationDomain('translation'); 28 | $parameterDefinition->setDefaultValue('42'); 29 | 30 | $this->assertEquals($parameterDefinition, $configurationManager->get('path')); 31 | $this->assertFalse($configurationManager->has('foo')); 32 | $this->assertTrue(is_array($configurationManager->getDefinedParameters())); 33 | 34 | $this->expectException('Exception'); 35 | $configurationManager->get('foo'); 36 | } 37 | 38 | public function testTypeMissing() 39 | { 40 | $config = [ 41 | 'path' => [ 42 | 'label' => 'test', 43 | 'options' => ['opt' => 'val'], 44 | 'translation_domain' => 'translation', 45 | ], 46 | ]; 47 | 48 | $this->expectException('LogicException'); 49 | new ConfigurationManager($config); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/FieldTypeManagerTest.php: -------------------------------------------------------------------------------- 1 | createConfiguredMock(FieldTypeInterface::class, [ 14 | 'getName' => 'text', 15 | ]); 16 | 17 | $fieldTypeNumber = $this->createConfiguredMock(FieldTypeInterface::class, [ 18 | 'getName' => 'number', 19 | ]); 20 | 21 | $fieldTypeManager = new FieldTypeManager(); 22 | $fieldTypeManager->addFieldType($fieldTypeText); 23 | $fieldTypeManager->addFieldType($fieldTypeNumber); 24 | 25 | $this->assertSame($fieldTypeManager->getField('text'), $fieldTypeText); 26 | $this->assertSame($fieldTypeManager->getField('number'), $fieldTypeNumber); 27 | 28 | $this->expectExceptionMessage('Unknown parameter type "date"'); 29 | $fieldTypeManager->getField('date'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/Transformer/ArrayTransformerTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'entry1-1' => 'foo', 15 | 'entry1-2' => 'bar', 16 | ], 17 | 'entry2' => 'baz', 18 | ]; 19 | 20 | $transformer = new ArrayTransformer(); 21 | $this->assertEquals(null, $transformer->transform(null)); 22 | $this->assertEquals(null, $transformer->transform('invalid serialized')); 23 | $this->assertEquals($data, $transformer->transform(serialize($data))); 24 | } 25 | 26 | public function testReverseTransform() 27 | { 28 | $data = [ 29 | 'entry1' => [ 30 | 'entry1-1' => 'foo', 31 | 'entry1-2' => 'bar', 32 | ], 33 | 'entry2' => 'baz', 34 | ]; 35 | 36 | $transformer = new ArrayTransformer(); 37 | $this->assertEquals(null, $transformer->reverseTransform(null)); 38 | $this->assertEquals(serialize($data), $transformer->reverseTransform($data)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/Transformer/BooleanTransformerTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(false, $transformer->transform(null)); 14 | $this->assertEquals(false, $transformer->transform('0')); 15 | $this->assertEquals(false, $transformer->transform(0)); 16 | $this->assertEquals(true, $transformer->transform(1)); 17 | $this->assertEquals(true, $transformer->transform(true)); 18 | $this->assertEquals(true, $transformer->transform('true')); 19 | $this->assertEquals(true, $transformer->transform('false')); 20 | } 21 | 22 | public function testReverseTransform() 23 | { 24 | $transformer = new BooleanTransformer(); 25 | $this->assertEquals(0, $transformer->reverseTransform(false)); 26 | $this->assertEquals(1, $transformer->reverseTransform(true)); 27 | $this->assertEquals(0, $transformer->reverseTransform('0')); 28 | $this->assertEquals(1, $transformer->reverseTransform('1')); 29 | $this->assertEquals(0, $transformer->reverseTransform(0)); 30 | $this->assertEquals(1, $transformer->reverseTransform(1)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/Transformer/CallbackTransformerTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(2, $transformer->transform(1)); 16 | $this->assertEquals(6, $transformer->transform(5)); 17 | } 18 | 19 | public function testReverseTransform() 20 | { 21 | $transformer = new CallbackTransformer(function() {}, function($value) { 22 | return $value - 1; 23 | }); 24 | $this->assertEquals(2, $transformer->reverseTransform(3)); 25 | $this->assertEquals(9, $transformer->reverseTransform(10)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Transformer/ArrayTransformer.php: -------------------------------------------------------------------------------- 1 | transform = $transform; 24 | $this->reverseTransform = $reverseTransform; 25 | } 26 | 27 | public function transform($data) 28 | { 29 | return \call_user_func($this->transform, $data); 30 | } 31 | 32 | public function reverseTransform($data) 33 | { 34 | return \call_user_func($this->reverseTransform, $data); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Transformer/TransformerInterface.php: -------------------------------------------------------------------------------- 1 | parameterManager = $parameterManager; 16 | } 17 | 18 | public function getFunctions(): array 19 | { 20 | return [ 21 | new TwigFunction('configuration_parameter', [$this, 'getParameterValue']), 22 | ]; 23 | } 24 | 25 | public function getParameterValue(string $key, mixed $default = null): mixed 26 | { 27 | return $this->parameterManager->get($key, $default); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sherlockode/configuration-bundle", 3 | "description": "Symfony bundle for user-defined configuration values", 4 | "license": "MIT", 5 | "type": "symfony-bundle", 6 | "authors": [ 7 | { 8 | "name": "Sherlockode", 9 | "email": "contact@sherlockode.fr" 10 | } 11 | ], 12 | "require": { 13 | "php": "^8.0", 14 | "symfony/framework-bundle": "^6.0 || ^7.0", 15 | "symfony/validator": "^6.0 || ^7.0", 16 | "twig/twig": "^3.0" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "^9.6" 20 | }, 21 | "autoload": { 22 | "psr-4": { "Sherlockode\\ConfigurationBundle\\": "" } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./ 6 | 7 | 8 | ./Resources 9 | ./Tests 10 | ./vendor 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ./Tests 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------