此软件包最新版本(6.4.2)没有提供许可证信息。


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',
    );
    ...
}