manuelj555 / sf-utils
Requires
- php: ^8.1
- symfony/framework-bundle: ^5.2|^6.0
README
包含用于Symfony >= 5项目的实用类
安装
composer require manuelj555/sf-utils ^6.0@dev
配置
在 config/bundles.php 中添加为bundle
<?php return [ ... Optime\Util\OptimeUtilBundle::class => ['all' => true], ];
配置选项
创建/调整文件 config/packages/optime_utils.yaml
optime_util: locales: [en, es, pt] # Configuración opcional default_locale: "%kernel.default_locale%" # Configuración opcional use_ajax_twig_loader: false # Si es true, activa el ajax_loader para twig
使用
Optime\Util\Exception\DomainException
用于在需要抛出域异常时(即,将被捕获和控制作为业务流程错误的一部分的异常)的类,即,将已批准的内容再次批准、拒绝无法拒绝的内容、退出不足等。
Optime\Util\Exception\ValidationException
当需要抛出域验证异常时(即,在执行业务流程时出现数据错误)的类,例如,字符串格式、空值等。
这个类在将异常转换为Symfony表单错误时很有用
try { // proceso }catch(\Optime\Util\Exception\ValidationException $e){ // agregar error a un form de Symfony: $e->addFormError($form, $translator); // agregar un flash: $this->addFlash("error", $e->getDomainMessage()->trans($translator)); }
Optime\Util\Batch\BatchProcessingResult
用于获取批量处理结果信息的实用类。例如,在加载CSV时,我们可以反映该类中正确处理和有处理问题的元素。
Optime\Util\Validator\DomainValidator
使用Symfony验证器的类,并允许轻松集成Symfony验证器与该库的域异常。如果存在验证错误,可以抛出ValidationException。
try { $domainValidator->handle($model); } catch (\Optime\Util\Exception\ValidationException $e) { $e->addFormError($form, $translator); }
Optime\Util\TranslatableMessage
允许定义可翻译消息的实用类。它用于此库的域异常。示例
try { throw new DomainException("error.invalid_value"); } catch (\Optime\Util\Exception\DomainException $e) { $this->addFlash('error', $e->getDomainMessage()->trans($translator)); } // Otro caso: try { throw new DomainException(new TranslatableMessage( "error.invalid_value", ['{invalid_value}' => 'aaa'], 'validators' // este es el domino de traducción. )); } catch (\Optime\Util\Exception\DomainException $e) { $this->addFlash('error', $e->getDomainMessage()->trans($translator)); }
表单中的翻译
有一些实用类用于处理可翻译字段,专注于扩展Doctrine的翻译,但也可以通用使用。
涉及的类
Optime\Util\Translation\TranslationsAwareInterface
该接口应由所有包含并希望管理可翻译属性的对象实现。应实现两个方法来获取或设置从数据源加载实体或对象的locale。
该接口的目的是管理包含@Gedmo\Locale注解的类属性。请参阅locale属性文档此处。
可以使用TranslationsAwareTrait简化接口的实现
<?php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use Optime\Util\Translation\TranslationsAwareInterface; use Optime\Util\Translation\TranslationsAwareTrait; /** * @ORM\Entity() */ class Entidad implements TranslationsAwareInterface { use TranslationsAwareTrait; ... }
使用trait,将接口方法和具有@Gedmo\Locale注解的属性添加到接口中。
Optime\Util\Translation\TranslatableContent
这是一个包含不同语言文本的值对象数组,数组的每个索引是locale,其值是该locale的文本。
Optime\Util\Translation\Translation
这是管理实体翻译的主要类。它提供了创建或持久化TranslatableContent的方法。
<?php $translation = ... obtenemos el servicio Optime\Util\Translation\Translation ########## Carga/Creación de un TranslatableContent ######### // traduccion nueva: $newContent = $translation->newContent([ 'en' => 'Hi', 'es' => 'Hola', ]); // traduccion nueva a partir de un unico string: $newContent = $translation->fromString('Hi'); // todos los locales tendrán el mismo texto // Obtener traducciones existentes en una entidad. $object = $repository->find(1); // object debe implementar TranslationsAwareInterface $translation->refreshInDefaultLocale($object); // importante refrescar el objeto en el locale por defecto de la app. $titleTranslations = $translation->loadContent($object, 'title'); $descriptionTranslations = $translation->loadContent($object, 'description'); // Todos los métodos anteriores retornan una instancia de TranslatableContent ########## Persitencia de un TranslatableContent ######### $titleContent = $translation->newContent([ 'en' => 'Hi', 'es' => 'Hola', ]); $newObject = new EntityClass(); // EntityClass debe implementar TranslationsAwareInterface $translation->refreshInDefaultLocale($newObject); // importante refrescar el objeto en el locale por defecto de la app. $newObject->setTitle((string)$titleContent); // castear a string retorna el valor en el locale por defecto. $persister = $translation->preparePersist($newObject); $persister->persist('title', $titleContent); $entityManager->persist($newObject); $entityManager->flush(); // Actualizando traducciones $object = $repository->find(1); // object debe implementar TranslationsAwareInterface $translation->refreshInDefaultLocale($object); // importante refrescar el objeto en el locale por defecto de la app. $titleTranslations = $translation->loadContent($object, 'title'); $titleTranslations->setValues(['en' => 'Other title', 'es' => 'Otro titulo']); $descriptionTranslations = $translation->fromString('Other Description'); $persister = $translation->preparePersist($object); $persister->persist('title', $titleTranslations); $persister->persist('description', $titleTranslations); $entityManager->persist($object); $entityManager->flush();
可以在Optime\Util\Translation\Translation中使用并包含的其他类包括
- Optime\Util\Translation\TranslatableContentFactory
newInstance(array $contents = []): TranslatableContentfromString(string $content): TranslatableContentload(TranslationsAwareInterface $entity, string $property): TranslatableContent
- Optime\Util\Translation\Persister\TranslatableContentPersister
prepare(TranslationsAwareInterface $targetEntity): PreparedPersister
- Optime\Util\Translation\Persister\PreparedPersister
persist(string $property, TranslatableContent $translations): void
前面的类和方法可以直接从服务 Optime\Util\Translation\Translation 使用。
在表单中使用
为了实现带有翻译字段的表单,我们有两种选择。
Optime\Util\Form\Type\TranslatableContentType
这种表单类型与 TranslatableContent 类协同工作,允许渲染平台配置的本地化字段。
当不直接使用 doctrine 实体时使用。
使用示例
<?php public function formAction(Request $request) { $data = [ 'title' => null, 'description' => $translation->newContent(), ]; $form = $this->createFormBuilder($data) ->add('title', TranslatableContentType::class) ->add('description', TranslatableContentType::class, [ 'type' => TextareaType::class, ]) ->getForm(); if ($form->isSubmitted()) { dump($form['title']->getData()); // TranslatableContent con los datos en cada idioma. dump($form['description']->getData()); // TranslatableContent con los datos en cada idioma. } }
Optime\Util\Form\Type\AutoTransFieldType
这种表单类型用于直接与 doctrine 实体一起工作,内部负责加载可翻译字段的翻译,并在提交表单时持久化这些翻译。
当与 doctrine 实体一起工作时使用。
使用示例
<?php use Optime\Util\Translation\TranslationsFormHandler; public function formAction(Request $request, TranslationsFormHandler $formHandler) { $entityObject = new EntityClass(); $form = $this->createFormBuilder($entityObject) ->add('title', AutoTransFieldType::class) ->add('description', AutoTransFieldType::class, [ 'type' => TextareaType::class, ]) ->getForm(); $form->handleRequest($request); if ($form->isSubmitted()) { dump($form['title']->getData()); // retorna el string en locale por defecto dump($form['description']->getData()); // retorna el string en locale por defecto // para persistir las traducciones se debe llamar a: $formHandler->persist($form); // si no se llama a esté método, no se guardarán las traducciones. $entityManager->persist($entityObject); $entityManager->flush(); } } public function formActionAutoSave(Request $request) { $entityObject = new EntityClass(); $form = $this->createFormBuilder($entityObject, [ 'auto_save_translations' => true, // activamos guardado automatico. ]) ->add('title', AutoTransFieldType::class) ->add('description', AutoTransFieldType::class, [ 'auto_save' => true, // activamos guardado automatico para este campo. ]) ->getForm(); $form->handleRequest($request); if ($form->isSubmitted()) { // No hay que hacer nada con las traducciones, el auto_save ya // hace el trabajo de persistirlas. $entityManager->persist($entityObject); $entityManager->flush(); } } public function formActionManualAutoSave(Request $request, TranslationsFormHandler $formHandler) { $entityObject = new EntityClass(); $form = $this->createFormBuilder($entityObject) ->add('title', AutoTransFieldType::class) ->add('description', AutoTransFieldType::class, [ 'auto_save' => true, ]) ->getForm(); $form->handleRequest($request); if ($form->isSubmitted()) { // Hacemos flush del auto save. // Util cuando no tenemos acceso al form y queremos // hacer la persitencia de los AutoTransFieldType // en un sitio especifico. $formHandler->flushAutoSave(); $entityManager->persist($entityObject); $entityManager->flush(); } }
使用翻译时的重要注意事项
当加载或持久化翻译时,重要的是确保实体已加载到平台的默认本地化中,而不是 URL 的本地化。否则,翻译值将被保存在非预期的本地化中。
因此,为了加载或持久化翻译,必须已加载实体到默认本地化,或使用以下代码以使实体在默认本地化中刷新:
<?php $translation = ... obtenemos el servicio Optime\Util\Translation\Translation $object = $repository->find(1); // importante refrescar el objeto en el locale por defecto de la app. $translation->refreshInDefaultLocale($object); // Se debe refrescar el objeto antes de hacerle algún cambio, ya que al refrescar // se revierten todos los posibles cambios no guardados en la entidad. $newContent = $translation->newContent([ 'en' => 'Hi', 'es' => 'Hola', ]); $object->setTitle((string)$titleContent); $persister = $translation->preparePersist($object); $persister->persist('title', $titleContent); $entityManager->flush();
如果尝试加载或持久化翻译而实体不在默认本地化中,应用程序将抛出一个异常,指示错误。
控制器属性
Optime\Util\Http\Controller\HandleAjaxForm
这是一个属性类型类,允许在 AJAX 请求时将无效表单的响应状态更改到 400 (Bad Request)。这对于处理表单并通过 AJAX 显示验证错误而无需重新加载页面非常有用。
此外,该类允许在 AJAX 请求中停止重定向,例如,当保存表单并执行重定向时,我们可以停止重定向并将响应状态转换为 200,以便在通过 AJAX 提交表单时。
示例
// Controlador: use Optime\Util\Http\Controller\HandleAjaxForm; #[HandleAjaxForm] public function formAction(Request $request) { $form = $this->createForm(FormType::class); $form->handleRequest($request); if ($form->isSubmitted() and $form->isValid()) { $entityManager->persist($form->getData()); $entityManager->flush(); } ... }
$.post('url', $form.serialize()).done(html => { // guardado con exito. }).fail(res => { // Usar el atributo #[HandleAjaxForm] en el controlador. // hace que la petición ajax devuelva un statusCode 400 // cuando el formulario es inválido if (res.status == 422) { // Si es Bad Request, actualizamos html con errores de // validación $form.html(res.responseText); } })
该类在构造函数中接收多个参数
use Optime\Util\Http\Controller\HandleAjaxForm; #[HandleAjaxForm( type: string|null, invalidStatus: Response::HTTP_UNPROCESSABLE_ENTITY, preventRedirect: true, replaceRedirectContent: true )] public function formAction(Request $request);
type默认为 null,可以指定一个表单类型,以便仅在表单与类型匹配时激活处理器。这仅在动作处理多个表单时有用。invalidStatus当表单无效时返回的 HTTP 状态。默认返回 400。preventRedirect指示是否应在提交和成功处理表单时避免任何重定向。replaceRedirectContent指示是否应替换响应内容,如果为 true,则响应内容将为 "Ok"。
Optime\Util\Http\Controller\PartialAjaxView
这是一个属性类型类,允许从仅包含 HTML 的响应中返回一个或多个部分,而不是整个内容。
这在页面可以以直接通过浏览器 URL 加载,也可以通过 JavaScript 以 AJAX 方式加载,而只需部分 HTML 时非常有用。
示例
// Controlador: use Optime\Util\Http\Controller\PartialAjaxView; #[HandleAjaxForm] #[PartialAjaxView] public function formAction(Request $request) { $form = $this->createForm(FormType::class); $form->handleRequest($request); if ($form->isSubmitted() and $form->isValid()) { $entityManager->persist($form->getData()); $entityManager->flush(); } ... }
{% extends 'layout.html.twig' %} {% block body %} <div class="container"> <h1>Titulo Form</h1> {% ajax_view %} {# El tag ajax_view permite que si la peticióne es ajax, solo se devuelva como contenido html lo que haya dentro del tag. #} {{form(form)}} {% end_ajax_view %} </div> {% endblock body %}
$.post('url', $form.serialize()).fail(res => { if (res.status == 400) { // Si es Bad Request, actualizamos el form. // Lo importante acá es que el html retornado solo // contiene lo que hay dentro del tag "ajax_view" $form.html(res.responseText); } })
该类在构造函数中接收多个参数
use Optime\Util\Http\Controller\PartialAjaxView; #[PartialAjaxView( name: 'default' string|array, method: null|string, ignoreOnEmpty: false, )] public function formAction(Request $request);
name默认为 'default',用于指示要从中提取的、包含在 "ajax_view" 标签中的 section 或多个 section。method允许指定 HTTP 方法(get、post 等),以便仅当请求是指定类型时才激活属性。ignoreOnEmpty如果为 true,则表示如果没有找到 "ajax_view" 标签或其内容为空,则应返回整个页面。如果为 false,则返回空字符串。
其他使用示例
以下示例将基于以下 twig:
{% extends 'layout.html.twig' %} {% block body %} <div class="container"> {% ajax_view %} {% ajax_view header %} <h1>Titulo Form</h1> {% end_ajax_view %} {% ajax_view table %} <table> ... </table> {% end_ajax_view %} {% end_ajax_view %} </div> {% endblock body %}
这个模板有三个ajax_view标签,其中两个有名称(header和table),一个没有名称,默认取名为"default",如果未指定名称。
指定返回的节点的示例
use Optime\Util\Http\Controller\PartialAjaxView; #[PartialAjaxView("table")] public function index(Request $request);
在这个例子中,如果请求是ajax类型,则只返回被{% ajax_view table %}标签包裹的部分,即使在该模板中有多个标签。
根据请求类型返回不同部分
use Optime\Util\Http\Controller\PartialAjaxView; #[PartialAjaxView("table", method: "post")] #[PartialAjaxView("header")] public function index(Request $request); #[PartialAjaxView("table", method: "post")] #[PartialAjaxView] public function other(Request $request);
对于index方法,如果请求是ajax且类型为"post",将返回"table"部分,否则将返回"header"部分。
对于other方法,如果请求是ajax且类型为"post",将返回"table"部分,否则将返回"default"部分。
返回多个部分
use Optime\Util\Http\Controller\PartialAjaxView; #[PartialAjaxView(["header", "table"])] public function index(Request $request);
这是一个特殊情况,如果需要返回多个部分,则需要将它们作为数组传递给属性。这样,当请求是ajax时,结果将是一个JSON类型的响应,其中的部分作为JSON对象的索引,而值是相应部分的HTML内容。
这很有用,例如,如果我们有一个带有ajax过滤器的页面,并且需要在过滤时更新一个列表和一个位于该列表远端的计数器,那么我们可以返回两个部分,一个用于列表,另一个用于计数器。
use Optime\Util\Http\Controller\PartialAjaxView; #[PartialAjaxView(["list_counter", "list_data"])] public function index(Request $request);
{% extends 'layout.html.twig' %} {% block body %} <div class="container"> <h1> Titulo Form {% ajax_view list_counter %} <span>#{{ items|count }}</span> {% end_ajax_view %} </h1> {% ajax_view list_data %} <table> ... </table> {% end_ajax_view %} </div> {% endblock body %}
$.get('ajax-page').done(json => { // podemos hacer lo que consideremos, actualizar esas secciones, etc. console.log(json.list_counter); // <span>#24</span> console.log(json.list_data); // <table>...</table> });