optimeconsulting / sf-utils
Requires
- php: ^8.1
- symfony/framework-bundle: ^5.2|^6.0
- symfony/uid: ^5.0|^6.0
Suggests
- phpoffice/phpspreadsheet: ^1.0|^2.0
- stof/doctrine-extensions-bundle: ^1.7
- dev-master
- 6.4.x-dev
- 6.4.2
- 6.4.1
- 6.4.0
- 6.3.x-dev
- 6.3.8
- 6.3.7
- 6.3.6
- 6.3.5
- 6.3.4
- 6.3.3
- 6.3.2
- 6.3.1
- 6.3.0
- 6.2.x-dev
- 6.2.24
- 6.2.23
- 6.2.22
- 6.2.21
- 6.2.20
- 6.2.19
- 6.2.18
- 6.2.17
- 6.2.16
- 6.2.15
- 6.2.14
- 6.2.13
- 6.2.12
- 6.2.11
- 6.2.10
- 6.2.9
- 6.2.8
- 6.2.7
- 6.2.6
- 6.2.5
- 6.2.4
- 6.2.3
- 6.2.2
- 6.2.1
- 6.2.0
- 6.1.x-dev
- 6.1.30
- 6.1.29
- 6.1.28
- 6.1.27
- 6.1.26
- 6.1.25
- 6.1.24
- 6.1.23
- 6.1.22
- 6.1.21
- 6.1.20
- 6.1.19
- 6.1.18
- 6.1.17
- 6.1.16
- 6.1.15
- 6.1.14
- 6.1.13
- 6.1.12
- 6.1.11
- 6.1.10
- 6.1.9
- 6.1.8
- 6.1.7
- 6.1.6
- 6.1.5
- 6.1.4
- 6.1.3
- 6.1.2
- 6.1.1
- 6.1.0
- 6.0.x-dev
- 6.0.9
- 6.0.8
- 6.0.7
- 6.0.6
- 6.0.5
- 6.0.4
- 6.0.3
- 6.0.2
- 6.0.1
- 6.0.0
- 5.0.x-dev
This package is auto-updated.
Last update: 2024-08-30 18:27:26 UTC
README
Repo 包含用于 Symfony 项目(>=5)的实用类
安装
composer require optimeconsulting/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
使用实体翻译的额外配置
重要 注意:截至(2024年5月)这些 doctrine 扩展与 doctrine >= 3.0 不兼容,应使用 2.x。
必须安装库 https://symfony.com.cn/bundles/StofDoctrineExtensionsBundle/current/installation.html
配置翻译扩展
config/packages/doctrine.yaml
doctrine: orm: entity_managers: default: mappings: gedmo_translatable: type: attribute prefix: Gedmo\Translatable\Entity dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Translatable/Entity" alias: GedmoTranslatable # (optional) it will default to the name set for the mapping is_bundle: false
config/packages/stof_doctrine_extensions.yaml
stof_doctrine_extensions: default_locale: en orm: default: translatable: true
使用
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 翻译扩展,但也可通用使用。
重要
为了在实体中使用翻译,此 bundle 需要安装 doctrine 扩展,特别是翻译扩展。因此,请安装并遵循 StofDoctrineExtensionsBundle 的文档。
涉及的类
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
该类是一个“值对象”,包含一个包含不同语言文本的数组,数组的每个索引是一个区域,其值是相应区域的文本。
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 = []): TranslatableContent
fromString(string $content): TranslatableContent
load(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处理表单并希望通过javascript在客户端显示错误而无需重新加载页面非常有用。
此外,该类允许在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
这是一个属性类,允许从 Twig HTML 响应中只返回一个或多个部分,而不是全部内容。
这在对页面既可以通过浏览器直接访问,也可以通过 JavaScript 的 AJAX 来加载,而我们只需要页面的一部分内容时非常有用。
示例
// 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" 标签中提取的某个或某些部分。method
允许指定一个 HTTP 方法(如 get, post 等),这样属性只有在请求类型匹配时才会激活。ignoreOnEmpty
如果为 true,则表示如果没有在 Twig 中找到 "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 %}
这个 Twig 有三个 "ajax_view" 标签,两个带有名称(header 和 table),一个没有名称,默认名称为 "default",因为没有指定。
指定要返回的部分示例
use Optime\Util\Http\Controller\PartialAjaxView; #[PartialAjaxView("table")] public function index(Request $request);
在这种情况下,如果请求是 AJAX,则只返回被 {% ajax_view table %}
标签包裹的部分,尽管在该 Twig 中有多个标签。
根据请求类型返回部分
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> });
Optime\Util\Http\Request\ArgumentValue\LoadFromRequestContent
这是一个属性类,允许使用 Symfony 序列化器将来自 application/json
请求的数据加载到对象或数组中。
示例
// Controlador: use Optime\Util\Http\Request\ArgumentValue\LoadFromRequestContent; public function actionOne(#[LoadFromRequestContent] array $userData) { // es equivalente a: $userData2 = json_decode($request->getContent(), false); ... } public function actionTwo(#[LoadFromRequestContent] UserDataRequest $userData) { // es equivalente a: $userData2 = $serializer->deserialize( $request->getContent(), UserDataRequest::class, $request->getContentType() ?? 'json', ); ... } /** * Cuando colocar el tipo en el parametro da problemas * (Ejemplo con entidades de doctrine) * Se puede indicar el tipo en el Atributo. */ public function actionThree( #[LoadFromRequestContent(UserDataRequest::class)] $userData ) { // es equivalente a: $userData2 = $serializer->deserialize( $request->getContent(), UserDataRequest::class, $request->getContentType() ?? 'json', ); ... }