ZF2 Translate in Controller

If you want to

use the translator in controller like in view, just like that:

$this->translate('Hello')

instead of ugly:

$this->getServiceLocator()->get('translator')->translate('Hello')

You have to write own controller plugin, just like view helper Zend\I18n\View\Helper\Translate.
Of course, you can invoke the plugin with the same signature:
__invoke($message, $textDomain = null, $locale = null)

To register a new plugin, put these lines in your configuration module.config.php:

'controller_plugins' => array(
    'factories' => array(
        'translate' => 'Application\Controller\Plugin\Translate',
    ),
),

Now, create your own plugin:

< ?php

namespace Application\Controller\Plugin;

use Zend\Mvc\Controller\Plugin\AbstractPlugin;

use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;

use Zend\I18n\Translator\Translator;

class Translate implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $serviceLocator = $serviceLocator->getController()->getServiceLocator();
        $serviceFactory = new TranslatorServiceFactory();
        $translator = $serviceFactory->createService($serviceLocator);
        
        return new TranslatorProxy($translator);
    }
}

final class TranslatorProxy extends AbstractPlugin
{
    private $translator;
 
public function __construct(Translator $translator) { $this->translator = $translator; } public function __invoke($message, $textDomain = 'default', $locale = null) { return $this->translator->translate($message, $textDomain, $locale); } public function __call($method, $args) { return call_user_func_array([$this->translator, $method], $args); } public static function __callstatic($method, $args)
{ return call_user_func_array([$this->translator, $method], $args); } }

How it works?

You see, the ServiceLocator passed in

createService(ServiceLocatorInterface $serviceLocator)

factory in configuration space controller_plugins, does have no access to the Config service in ServiceLocator in controller. So you cannot get the configuration and create the Translate object via TranslatorServiceFactory.

Instead of that, you can access to the ServiceLocator assigned to the controller for which our helper has been invoked, by typing $serviceLocator->getController().

Of course, $serviceLocator passed in createService method is instance of Zend\Mvc\Controller\PluginManager.

Why proxy?

The object returned via plugin factory has to implement

Zend\Mvc\Controller\Plugin\PluginInterface

which is abstractly implemented in

Zend\Mvc\Controller\Plugin\AbstractPlugin

so we created proxy object to forward all calls from our plugin to the Translate object.

Hope it helped!

 

How to send template and layout mail with Zend_Mail

Zend_Mail provides a great and simple in use and configuration mechanizm to send emails. The problem begins when you would like to specify fully templated and layouted messages.

In my current project I have several kinds of mails: customer invoices and messages, users notifications, admin notifications, webmaster email about critical errors in scheduled system tasks. In this case the Zend_Layout fits perfectly to redner rich text content by Zend_View, but it is implemented in Zend_Mail, wchih provies simply setBodyText() and setBodyHTML() methods.

This inconvenience is understandable by the way, mainly in context simpe, clear and flexible extendable code of Zend Framework. We will strive to extend the functionality od Zend_Mail following ZF developers concepts.

Overview

Writing class extending Zend_Mail I kept a several concepts:

  • Messages should be both templated and layouted using Zend_View and Zend_Layout.
  • The email view scripts (templates) there are in main view scripts directory nested in subdirectory (as deep as you want).
  • … The same path story with layouts.
  • You can use this object excatly the same way as Zend_Mail. It behaviour the same way as parent until you set special options (like point to view script path or file to render in body).
  • … and object should keep Zend_Mail fluent interface (returning $this in setters) to provide method chaining fluent interface.
  • Pointed view file is rendered as a mail body.
  • You can use this object excatly the same way as Zend_Mail. It behaviour the same way as parent until you set special options (like point to view script path or file to render in body).

Zend_Mail application.ini configuration and extending application

Simply paste several lines to application.ini configuration, theare are self-commented, description is not neccessary at this point. We will use SMTP transport:

resources.mail.transport.type = smtp
resources.mail.transport.host = YOUR_HOSTNAME
resources.mail.transport.auth = login
resources.mail.transport.username = "YOUR_ACCOUNT"
resources.mail.transport.password = "YOUR_PASSWORD"
resources.mail.transport.register = true

resources.mail.defaultFrom.email = YOUR_ACCOUNT
resources.mail.defaultFrom.name = "MyService.com"
resources.mail.defaultReplyTo.email = YOUR_ACCOUNT
resources.mail.defaultReplyTo.name = "MyService.com"

In addition we will create tho additional directories and files:

  • /application/views/scripts/email/–  just add a subdirectory /email to existing view scripts directory.
  • /application/layouts/scripts/email/ – the same story as above
TIP: I have moved default configured layouts direcotry to /application/views/layouts/ to unify structure of application. Just change in application.ini this line:
resources.layout.layoutPath = APPLICATION_PATH "/modules/default/views/layouts/scripts/"

To test our class let’s create additional two files:

/application/layouts/scripts/email/html.phtml

< ?php echo $this->layout()->content ?>

--
Best Regards,
MyService.com

and scond one, the information about successfull account register with your own content:

/application/views/scripts/email/AccountRegister.phtml

Thanks for register.

ZentUtil_Mail class usage

Before we will write a code, let’s think abous its usage, wchih should be the same as in Zend_Mail documentation witch additional methods:

$mail = new ZendUtil_Mail('utf-8');
$mail->addTo('athlanster@gmail.com', 'Piotr Pelczar');
$mail->setSubject('Testowy mail z zenda');
$mail->setBodyView('AccountRegister.phtml');
$mail->send();

Above code should send mail from AccountRegister.phtml view script nested in html.phtml layout.

If you want to change layout simply call setViewLayoutScript($script) method with string or set false to disable layouts. For change paths setViewPathDirectory($path), setViewLayoutPathDirectory($path) are available.

ZendUtil_Mail extends Zend_Mail

ZendUtil_Mail has been extended by Zend_Mail and _prepareBody() method has been added. It is called just before parent::send() method.

NOTE: I have added ZendUtil_ namespace.

I hope it will help.

 

How to get single error message with Zend_Validate_EmailAddress validation

I have just started introducing Zend Framework when I had to face the problem with output multiple error messages in form while email address validation. Checking domain (whith is enabled by default) causes additional error messages indicates anomalies in hostname segment of provided by user email address. In result, we receive several errors assigned to one email field.

The problem is easy to slove by overriding default behaviour of Zend_Validate_EmailAddress clearing all messages generating while validation and setup a new single error message.

Simply add namespace MyOwn_ for own needs and provide class in file /libraries/MyOwn/Validate/EmailAddess.php

class MyOwn_Validate_EmailAddress extends Zend_Validate_EmailAddress
{
  const INVALID = 'emailAddressInvalid';

  protected $_messageTemplates = array(
    self::INVALID => "Invalid Email Address."
  );

  public function isValid($value)
  {
    parent::setOptions(array(
      'allow' => Zend_Validate_Hostname::ALLOW_DNS,
      'domain' => true,
      'mx' => true,
      'deep' => true)
    );

    if(!parent::isValid($value)) {
      $this->_messages = array(); // clear all previous messages
      $this->_error(self::INVALID);
      return false;
    }

    return true;
  }
}

And provide above custom validator to form element in /application/forms/AccountRegister.php:

class Form_AccountRegister extends Zend_Form
{
  public function init()
  {
    $this->setMethod('post')
         ->setName('Account_Register');
    
    $email = new Zend_Form_Element_Text('email');
    $email
      ->setLabel('Email address')
      ->addValidator(new ZendUtil_Validate_EmailAddress)
      ->setRequired(true);
    
    $this->addElement($email);
    
  }
}

NOTE:

  • In addition, you can simply translate the emailAddressInvalid message.
  • For sticklers, setting options in isValid method is hardcoded with look like a messy code, but it is quick-fix

Hope it will help.