Jenkins DSL generic multiple promotions - groovy

I've written most of our project's jobs/pipelines in DSL without any previous groovy experience but now I'm stuck at more advanced problem that I can't figure out.
I'm trying to implement a method that would add 1,2,n promotions to a job.
Below you can see a fully-working method that can add one promotion, and I expected it to work in such way that I'd just call the method twice if I needed another one but then I ran into my problem - only promotion that was created the latest would be generated.
/**
* #param job DSL job object
* #param promotionName Name of the promotion
* #param nextJobs Comma seperated string of jobs to trigger when promotion is executed
* #param deployers Comma seperated string IDs that can execute promotion
* #param params Array of parameters to pass to the next job [0] = key, [1] = value.
*/
static void addPromotion(def job, String promotionName, String nextJobs, String deployers, String[][] params){
job.properties {
promotions {
promotion {
name(promotionName)
icon("star-gold")
conditions {
manual(deployers)
}
actions {
downstreamParameterized {
trigger(nextJobs) {
parameters {
for (String[] param : params){
predefinedProp(param[0]+"=",param[1])
}
}
}
}
}
}
}
}
}
The way it would work, however, if I added another 'promotion' closure like this, however, this example would generate almost identical(name and name-1) promotions:
static void addPromotion(def job, String promotionName, String nextJobs, String deployers, String[][] params){
job.properties {
promotions {
promotion {
name(promotionName)
icon("star-gold")
conditions {
manual(deployers)
}
actions {
downstreamParameterized {
trigger(nextJobs) {
parameters {
for (String[] param : params){
predefinedProp(param[0]+"=",param[1])
}
}
}
}
}
}
promotion {
name("${promotionName}-1")
icon("star-gold")
conditions {
manual(deployers)
}
actions {
downstreamParameterized {
trigger(nextJobs) {
parameters {
for (String[] param : params){
predefinedProp(param[0]+"=",param[1])
}
}
}
}
}
}
}
}
}
Is it possible to re-use closures in some way and populate the variables from a different method maybe? Or any other ideas?

This is how I solved it.
Generic promotion object part:
/**
* Adds 'promoted-builds' plugin configuration to job
**/
class Promotions {
public def job
public String promotionName
public String nextJobs
public String deployers
public String [][] params
/**
* #param job DSL job object
* #param promotionName Name of the promotion
* #param nextJobs Comma seperated string of jobs to trigger when promotion is executed
* #param deployers Comma seperated string IDs that can execute promotion
* #param params Array of parameters to pass to the next job [0] = key, [1] = value.
*/
public Promotions(Object jobName, String promotionName, String nextJobs, String deployers, String[][] params){
this.job = jobName
this.promotionName = promotionName
this.nextJobs = nextJobs
this.deployers = deployers
this.params = params
}
static void addPromotions(Promotions ... jobPromotions){
// Assuming the same job is provided as arguments
jobPromotions[0].job.properties {
promotions {
for (Promotions jobPromotion : jobPromotions){
promotion {
name(jobPromotion.promotionName)
// star-gold, star-silver
icon("star-gold")
conditions {
manual(jobPromotion.deployers)
}
actions {
downstreamParameterized {
trigger(jobPromotion.nextJobs) {
parameters {
for (String[] param : jobPromotion.params){
predefinedProp(param[0],param[1])
}
}
}
}
}
}
}
}
}
}
}
And then I prepare my params and pass them to the promotion constructor, and in the end I call addPromotions() and pass all my constructed objects to it:
def nextJobs = "${Configuration.repoName}-${branchName}-deploy-to-perf"
def deployers = "developer"
def params = [["VERSION", "\${VERSION}"],
["SOURCE_GIT_COMMIT", "\${SOURCE_GIT_COMMIT}"]] as String[][]
def promo1 = new Promotions(job, "Promote to PERF", nextJobs, deployers, params)
def nextJobs2 = "../master/${Configuration.repoName}-${branchName}-to-prod-dtr"
def deployers2 = "admin"
def params2 = [["VERSION", "\${VERSION}"],
["SOURCE_GIT_COMMIT", "\${SOURCE_GIT_COMMIT}"]] as String[][]
def promo2 = new Promotions(job, "Promote to PROD", nextJobs2, deployers2, params2)
Promotions.addPromotions(promo1, promo2)

Related

How automatically fill object path to avoid NPE in groovy?

I have such code:
a.b = null
a.b.c = 10 // here is NPE should be thrown
Is there way to avoid NPE by some automatic interception and initialization of b with new instance value?
In common case the object path may be long like this: a.b.c.d.e.f = 10
and any part of path may be met a null value
If you don't want to insert safe-navigation operators like a?.b?.c?.d?.e?.f = 10 you can use a macro to do this "automatically":
// I have not tried with assignment so you may need to fill a gap in the macro method
safe(a.b.c.d.e.f = 10)
// I have tested like this:
#Test
void testSafeOne() {
def objExp = null
def hashCode = safe(objExp.hashCode())
assert safe(hashCode.toString()) == null
}
#Test
void testSafeMany() {
def objExp = null
assert safe(objExp.hashCode().toString()) == null
}
#Test
void testSafeAttr() {
Integer number = null
assert safe(number.#MAX_VALUE.abs()) == null
}
#Test
void testSafeAssign() {
// TODO
}
You need to register this class through the extension mechanism:
package macros
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.ast.expr.*
import org.codehaus.groovy.macro.runtime.Macro
import org.codehaus.groovy.macro.runtime.MacroContext
import static org.codehaus.groovy.ast.ClassHelper.*
import static org.codehaus.groovy.ast.tools.GeneralUtils.*
import static org.apache.groovy.ast.tools.ExpressionUtils.isThisExpression
class SafeMacroMethods {
/**
* macro version of: <code>objectExpr.?methodName()</code>
*/
#Macro
static Expression safe(MacroContext context, MethodCallExpression expression) {
if (isImplicitThis(context)) {
expression.tap { safe = true; safe(context, objectExpression) }
}
}
/**
* macro version of: <code>objectExpr.?propertyName</code>
*/
#Macro
static Expression safe(MacroContext context, PropertyExpression expression) {
if (isImplicitThis(context)) {
expression.tap {
if (getClass() == PropertyExpression) {
it.#safe = true
} else { // AttributeExpression, etc.
def field = PropertyExpression.getDeclaredField('safe')
field.accessible = true
field.set(it, true)
}
safe(context, objectExpression)
}
}
}
/**
* macro version of: <code>targetExpr ?= sourceExpr</code>
*/
#Macro
static Expression safe(MacroContext context, BinaryExpression expression) {
if (isImplicitThis(context)) {
expression.tap { safe = true; safe(context, leftExpression); safe(context, rightExpression) }
}
}
protected
static Expression safe(MacroContext context, Expression expression) {
expression
}
private static boolean isImplicitThis(MacroContext context) {
context.call.with {
isThisExpression(objectExpression) && isImplicitThis()
}
}
}

Intervene template rendering

I have a controller method which I am using to "collect" variables to be assigned to template. I have overridden controller's render() method to merge "collected" and render parameters and assign them to template.
Example:
class Controller extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
private $jsVars = [];
protected function addJsVar($name, $value)
{
$this->jsVars[$name] = $value;
}
public function render($view, array $parameters = [], Response $response = null)
{
return parent::render($view, array_merge($parameters, ['jsVars' => $this->jsVars], $response);
}
public function indexAction()
{
// collect variables for template
$this->addJsVar('foo', 'bar');
return $this->render('#App/index.html.twig', ['foo2' => 'bar2']);
}
}
I just upgraded to Symfony 3.4 which complains that since Symfony4 I am not allowed to override render() method as it will be final.
How could I make it work seamlessly, i.e without defining a new method?
I know about Twig globals but these dont help me
I could use a service to collection variables and inject that service to Twig but that seems odd
Are there events I could listen, e.g TwigPreRender or smth?
You can render a controller from inside Twig like that:
{{ render(controller('App\\Controller\\YourController::yourAction', { 'args': 'hi' })) }}
Documentation here
Seems that there is no easy way.
Basically there are 2 options:
create your own template engine by extending current Symfony\Bundle\TwigBundle\TwigEngine
decorate current templating engine service templating.engine.mytwig
I chose the latter.
Few explanations:
I created service templating.engine.mytwig which decorates current engine templating.engine.twig. Class will get current ´TwigEngine` as input and I'll delegate most of the stuff to it
The class also needs to be twig extension by implementing \Twig_ExtensionInterface (or extending \Twig_Extension was sufficient for me). Also service needs to have tag twig.extension. Otherwise you'll end up having errors such as "Cannot find private service 'assetic' etc"
setParameter/getParameter are for collecting and returning parameters
Then I added shortcut methods to my Controller - setJsVar
Twig template requires also handling of those variables, preferably somewhere in the layout level. But that is not included here
One could you this solution to collect arbitrary template parameters, e.g if you want to assign from another method or whatever
It would be good idea to clear collected parameters after render
Was that all worth it? I dont know :) Cannot understand why Symfony team chose to make Controller::render final in the first place. But anyway here it is:
TwigEnging class:
namespace My\CommonBundle\Component\Templating\MyTwigEngine;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Bundle\TwigBundle\TwigEngine;
use Symfony\Component\HttpFoundation\Response;
class MyTwigEngine extends \Twig_Extension implements EngineInterface
{
/**
* #var TwigEngine $twig Original Twig Engine object
*/
private $twig;
/**
* #var array $parameters Collected parameters to be passed to template
*/
private $parameters = [];
/**
* MyTwigEngine constructor.
*
* #param TwigEngine $twig
*/
public function __construct(TwigEngine $twig)
{
$this->twig = $twig;
}
/**
* "Collects" parameter to be passed to template.
*
* #param string $key
* #param mixed $value
*
* #return static
*/
public function setParameter($key, $value)
{
$this->parameters[$key] = $value;
return $this;
}
/**
* Returns "collected" parameter
*
* #param string $key
* #return mixed
*/
public function getParameter($key, $default = null)
{
$val = $this->parameters[$key] ?? $default;
return $val;
}
/**
* #param string|\Symfony\Component\Templating\TemplateReferenceInterface $name
* #param array $parameters
*
* #return string
* #throws \Twig\Error\Error
*/
public function render($name, array $parameters = array())
{
return $this->twig->render($name, $this->getTemplateParameters($parameters));
}
/**
* #param string $view
* #param array $parameters
* #param Response|null $response
*
* #return Response
* #throws \Twig\Error\Error
*/
public function renderResponse($view, array $parameters = array(), Response $response = null)
{
return $this->twig->renderResponse($view, $this->getTemplateParameters($parameters), $response);
}
/**
* #param string|\Symfony\Component\Templating\TemplateReferenceInterface $name
*
* #return bool
*/
public function exists($name)
{
return $this->twig->exists($name);
}
/**
* #param string|\Symfony\Component\Templating\TemplateReferenceInterface $name
*
* #return bool
*/
public function supports($name)
{
return $this->twig->supports($name);
}
/**
* #param $name
* #param array $parameters
*
* #throws \Twig\Error\Error
*/
public function stream($name, array $parameters = array())
{
$this->twig->stream($name, $this->getTemplateParameters($parameters));
}
/**
* Returns template parameters, with merged jsVars, if there are any
* #param array $parameters
* #return array
*/
protected function getTemplateParameters(array $parameters = [])
{
$parameters = array_merge($this->parameters, $parameters);
return $parameters;
}
}
Decorator service (services.yml):
services:
templating.engine.mytwig:
decorates: templating.engine.twig
class: My\CommonBundle\Component\Templating\MyTwigEngine
# pass the old service as an argument
arguments: [ '#templating.engine.mytwig.inner' ]
# private, because you probably won't be needing to access "mytwig" directly
public: false
tags:
- { name: twig.extension }
Base controller alteration:
namespace My\CommonBundle\Controller;
use My\CommonBundle\Component\Templating\MyTwigEngine;
abstract class Controller extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
/**
* Allows to set javascript variable from action
*
* It also allows to pass arrays and objects - these are later json encoded
*
* #param string $name Variable name
* #param mixed $value - string|int|object|array
*
* #return static
*/
protected function setJsVar($name, $value)
{
/** #var MyTwigEngine $templating */
$templating = $this->getTemplating();
if (!$templating instanceof MyTwigEngine) {
throw new \RuntimeException(sprintf(
'Method %s is implemented only by %s', __METHOD__, MyTwigEngine::class
));
}
$jsvars = $templating->getParameter('jsVars', []);
$jsvars[$name] = $value;
$templating->setParameter('jsVars', $jsvars);
return $this;
}
/**
* Returns templating service
* #return null|object|\Twig\Environment
*/
private function getTemplating()
{
if ($this->container->has('templating')) {
$templating = $this->container->get('templating');
} elseif ($this->container->has('twig')) {
$templating = $this->container->get('twig');
} else {
$templating = null;
}
return $templating;
}
}

jobDataMap to pass parameters for Multiple Triggers in Quartz

Hi my code works with multiple triggers and i am trying to pass specific parameters associated with
each trigger using jobDataMap.But when i am trying to assign the map in my config.groovy to
jobDataMap i get a nullpointerexception
**This is the Map in my Config.groovy-->**
Query
{
Map
{
time.'0/5 * * * * ?' = ['T1']
time.'0/10 * * * * ?' = ['T2']
templates.'T1' = ['Date','FinshDate','Location']
templates.'T2' = ['TableName']
parameterValues.'T1' = ['2014071600','2014072000','Path']
parameterValues.'T2' = ['AppleData']
}
}
**This is my Quartz Job Code for multiple triggers ->**
import org.quartz.*
import org.quartz.Trigger
import static org.quartz.JobBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.TriggerBuilder.*;
import org.quartz.impl.StdSchedulerFactory;
import org.codehaus.groovy.grails.commons.GrailsApplication;
public class TrialJob
{
public static void main(String[] args)
{
String JobName
String GroupName
GrailsApplication grailsApplication;
Trigger trigger
def triggerList=[]
def jobList=[]
def cronList=["0/5 * * * * ?","0/10 * * * * ?","0/15 * * * * ?"]
// here i am creating 3 triggers which works fine
for(i in 0..2)
{
JobName="trigger"+Integer.toString(i)
GroupName = "Group"+Integer.toString(i)
println cronList[i]
JobDetail job = JobBuilder.newJob(TestJob.class).withIdentity(JobName,GroupName).build();
trigger= TriggerBuilder.newTrigger().withIdentity(JobName,GroupName).withSchedule(CronScheduleBuilder.cronSchedule(cronList[i])).build();
triggerList.add(trigger)
jobList.add(job)
}
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
for(j in 0..2)
{
// here i want to put the associated parameters for each trigger in the trigger list
// For Example 1) trigger 0--> triggerList[0].jobDataMap.put(['Date','FinshDate','Location'],['2014071600','2014072000','Path'])
// 2) trigger 1--> triggerList[1].jobDataMap.put(['TableName'],['AppleData'])
scheduler.scheduleJob(jobList[j],triggerList[j]);
println "torpido"
println j
}
//while(true){};
}
public static class TestJob implements Job
{
public void execute(JobExecutionContext context) throws JobExecutionException
{
HashMap<String, String> parameter = new HashMap();
parameter=context.getMergedJobDataMap()
println "Inside Execute"
}
}
}
how do i use jobDataMap inside the above for loop (it would be more
clear by looking at the comments inside the for loop) and access them
inside the execute method ?
I'm not grails expert but it seems that grails quartz scheduler plugin should be used.
Below You can find working code:
#Grab(group='org.quartz-scheduler', module='quartz', version='2.2.1')
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class CronTriggerExample {
public static void main( String[] args ) throws Exception {
def cronExpressions = ['0/5 * * * * ?', '0/10 * * * * ?', '0/20 * * * * ?']
def triggers = cronExpressions.collect { cron ->
TriggerBuilder
.newTrigger()
.withIdentity("trigger-$cron", "trigger-$cron-group")
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.usingJobData(new JobDataMap(['cron': cron]))
.build()
}
Scheduler scheduler = new StdSchedulerFactory().getScheduler()
scheduler.start()
triggers.each { trigger ->
def job = JobBuilder.newJob(HelloJob).withIdentity("$trigger.key.name-job", "$trigger.key.name-job-group").build()
scheduler.scheduleJob(job, trigger)
}
while(true){}
}
}
public class HelloJob implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
println "Hello Quartz with cron: ${context.mergedJobDataMap.getString('cron')}"
}
}
Job name, job group as well trigger name, trigger group must be unique. Other objects can be passed with JobDataMap. Is that clear now?

Multiple threads adding to child collection results in StaleObjectStateException

I have a situation where multiple threads load the same entity Job and then each thread adds to its child collection Set<JobError>. I can understand this exception if the parent itself was updated, but the only 'change' to the parent is the addition to the collection, even then?
Parent Entity:
#Entity
#Table(name = "JOB")
public class Job extends BaseEntity {
private Set<JobError> jobErrors = new HashSet<JobError>();
/**
* #return the jobErrors
*/
#OneToMany(mappedBy = "job", cascade = { CascadeType.PERSIST,
CascadeType.MERGE, CascadeType.REMOVE })
public Set<JobError> getJobErrors() {
return jobErrors;
}
/**
* #param jobErrors
* the jobErrors to set
*/
public void setJobErrors(Set<JobError> jobErrors) {
this.jobErrors = jobErrors;
}
/**
* Helper to take care of both sides of the association
* #param message
* #param currentProfileId
*/
public void addError(String message, Long currentProfileId,
String firstName, String lastName) {
JobError er = new JobError(message, currentProfileId, firstName,
lastName, this);
jobErrors.add(er);
}
}
Child Entity:
#Entity
#Table(name = "JOB_ERROR")
public class JobError extends BaseEntity {
private Job job;
public JobError(String description, Long profileId, String firstName,
String lastName, Job job) {
this.description = description;
this.profileId = profileId;
this.firstName = firstName;
this.lastName = lastName;
this.job = job;
}
/**
*
*/
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "JOB_ID", nullable = false)
public Job getJob() {
return job;
}
/**
* #param jobErrors
* the jobErrors to set
*/
public void setJob(Job job) {
this.job = job;
}
}
Service Code, this runs in multiple concurrent threads:
job = jobDao.findById(er.getJobId(), false);
for (Long profileId : er.getProfileIds()) {
// do stuff
try {
sendEmail(emailTemplateDto, user);
} catch (RuntimeException re) {
job.addError(re.getLocalizedMessage(), currentProfileId, profile.getPersonalData().getFirstName(), profile.getPersonalData().getLastName());
}
Once the service method returns which is annotated as #Transactional(propagation = Propagation.REQUIRED) the StaleObjectStateException is thrown:
2013-03-28 13:22:52,578 ERROR org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(324): - Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.test.project.domain.Job#2]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1950)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2594)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2494)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2821)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:113)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:185)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:76)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:467)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at $Proxy162.processSendEmail(Unknown Source)
at com.test.project.service.messaging.EmailRequestMessageListener.onMessage(EmailRequestMessageListener.java:57)
at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:560)
at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:498)
at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:467)
at org.springframework.jms.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:439)
at org.springframework.jms.listener.SimpleMessageListenerContainer.processMessage(SimpleMessageListenerContainer.java:311)
at org.springframework.jms.listener.SimpleMessageListenerContainer$2.onMessage(SimpleMessageListenerContainer.java:287)
at org.apache.activemq.ActiveMQMessageConsumer.dispatch(ActiveMQMessageConsumer.java:1321)
at org.apache.activemq.ActiveMQSessionExecutor.dispatch(ActiveMQSessionExecutor.java:131)
at org.apache.activemq.ActiveMQSessionExecutor.iterate(ActiveMQSessionExecutor.java:202)
at org.apache.activemq.thread.PooledTaskRunner.runTask(PooledTaskRunner.java:129)
at org.apache.activemq.thread.PooledTaskRunner$1.run(PooledTaskRunner.java:47)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
I can think of nothing else except for trying to save JobError directly. Currently I first load Job, add to the collection of JobError and then merge Job and hope the cascade.merge will take care of saving the child collection.
Any pointers will be appreciated.
I don't know if this is the cause of the exception in question, but if not it is going to cause problems down the line: HashSet is not a thread-safe collection, which means that if two threads call addError at the same time then one of the errors might not make it into the set. You'll either need to add the "synchronized" keyword to the addError method, or else you'll need to replace the HashSet with a thread-safe alternative, e.g. a ConcurrentLinkedQueue or ConcurrentHashMap

How to automatically escape variables in a Zend Framework 2 view

A lot of times in a Zend Framework 2 view I'll be calling $this->escapeHtml() to make sure my data is safe. Is there a way to switch this behaviour from a blacklist to a whitelist?
PS: Read an article from Padraic Brady that suggests that automatic escaping is a bad idea. Additional thoughts?
You could write your own ViewModel class which escapes data when variables are assigned to it.
Thanks to Robs comment, I extended the ZF2 ViewModel as follows:
namespace Application\View\Model;
use Zend\View\Model\ViewModel;
use Zend\View\Helper\EscapeHtml;
class EscapeViewModel extends ViewModel
{
/**
* #var Zend\View\Helper\EscapeHtml
*/
protected $escaper = null;
/**
* Proxy to set auto-escape option
*
* #param bool $autoEscape
* #return ViewModel
*/
public function autoEscape($autoEscape = true)
{
$this->options['auto_escape'] = (bool) $autoEscape;
return $this;
}
/**
* Property overloading: get variable value;
* auto-escape if auto-escape option is set
*
* #param string $name
* #return mixed
*/
public function __get($name)
{
if (!$this->__isset($name)) {
return;
}
$variables = $this->getVariables();
if($this->getOption('auto_escape'))
return $this->getEscaper()->escape($variables[$name]);
return $variables[$name];
}
/**
* Get instance of Escaper
*
* #return Zend\View\Helper\EscapeHtml
*/
public function getEscaper()
{
if (null === $this->escaper) {
$this->escaper = new EscapeHtml;
}
return $this->escaper;
}
}
In a Controller it could be used like this:
public function fooAction()
{
return new EscapeViewModel(array(
'foo' => '<i>bar</i>'
));
//Turn off auto-escaping:
return new EscapeViewModel(array(
'foo' => '<i>bar</i>'
),['auto_escape' => false]);
}
Question:
I would appreciate it if soemebody would comment, if this is best practice or if there is a better and ecp. more efficient and resource-saving way?

Resources