manuelj555/sf-utils

此包最新版本(6.1.16)没有可用的许可信息。

6.1.16 2022-05-02 19:47 UTC

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 = []): 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 显示验证错误而无需重新加载页面非常有用。

此外,该类允许在 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>
});