2

I use symfony2.4 and KNP doctrine behaviors translatable.

I have entity Site (for ID, host, enabled) and entity SiteTranslation (for translated fields: name, descriptions, …).

I use query to get results

$qb = $this->createQueryBuilder('s')
    ->addSelect('translation') // to eager fetch translations (optional)
    ->leftJoin('s.translations', 'translation') // or innerJoin ?
    ->orderBy('s.root', 'ASC')
    ->addOrderBy('s.lft', 'ASC');

I would like to print result in Twig. For ID, host and enabled fields from Site entity it's easy:

{{ item.id }}

But I can't print translated fields (name, description, …)

{{ item.name }}

It doesn't work.

Error message:

ContextErrorException: Warning: call_user_func_array() expects parameter 1 to be a valid >callback, class 'Net\ConBundle\Entity\SiteTranslation' does not have a method 'name' in >D:\Users...\vendor\knplabs\doctrine->behaviors\src\Knp\DoctrineBehaviors\Model\Translatable\TranslatableMethods.php line 140

Getters and setters for translatable fields are in SiteTranslation entity.

UPDATE:

I still didn't find a solution for an error.

Here is Site entity:

<?php
namespace Pnet\ConlocoBundle\Entity;

use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;


 /**
 * @UniqueEntity("host", message="site.host.unique", groups={"edit"})
 * @Gedmo\Tree(type="nested")
 * @ORM\Entity(repositoryClass="Pnet\ConlocoBundle\Entity\Repository\SiteRepository")
 * @ORM\Table(name="site")
 */
class Site
{
use ORMBehaviors\Translatable\Translatable;   // knp translatable strategy

/**
 * @ORM\Id
 * @ORM\Column(type="integer")
 * @ORM\GeneratedValue(strategy="AUTO")
 */
protected $id;


/**
 * @ORM\Column(type="string", length=40, unique=true)
 * @Assert\NotBlank(message="site.host.notBlank", groups={"edit"})
 * @Assert\Length(max = "40", maxMessage = "site.host.maxLength", groups={"edit"})
 */    
protected $host;

/**
 * @ORM\Column(type="string", length=255, nullable=true)
 * @Assert\Image()
 */
protected $image;     

/**
 * @ORM\Column(type="boolean")
 * @Assert\Choice(choices = {"1", "0"}, message = "site.isDefault.choice", groups={"edit"})
 */
protected $isDefault;

/**
 * @ORM\Column(type="boolean")
 * @Assert\Choice(choices = {"1", "0"}, message = "site.enabled.choice", groups={"edit"})
 */  
protected $enabled;

/**
 * @ORM\Column(type="string", length=64, nullable=true)
 */
protected $analytics;     

/**
 * @Gedmo\TreeLeft
 * @ORM\Column(name="lft", type="integer")
 */
private $lft;

/**
 * @Gedmo\TreeLevel
 * @ORM\Column(name="lvl", type="integer")
 */
private $lvl;

/**
 * @Gedmo\TreeRight
 * @ORM\Column(name="rgt", type="integer")
 */
private $rgt;

/**
 * @Gedmo\TreeRoot
 * @ORM\Column(name="root", type="integer", nullable=true)
 */
private $root;

/**
 * @Gedmo\TreeParent
 * @ORM\ManyToOne(targetEntity="Site", inversedBy="children")
 * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
 */
private $parent;

/**
 * @ORM\OneToMany(targetEntity="Site", mappedBy="parent")
 * @ORM\OrderBy({"lft" = "ASC"})
 */
private $children;


private $file;

public $idByFilter;

public $nameByFilter;    


/**
 * Proxy translations (Knp/Doctrine Behaviors)
 * An extra feature allows you to proxy translated fields of a translatable entity.
 * You can use it in the magic __call method of you translatable entity so that when
 * you try to call getName (for example) it will return you the translated value
 * of the name for current locale:  
 */
public function __call($method, $arguments)
{
    return $this->proxyCurrentLocaleTranslation($method, $arguments);
}  







/**
 * Get id
 *
 * @return integer 
 */
public function getId()
{
    return $this->id;
}

/**
 * Set host
 *
 * @param string $host
 * @return Site
 */
public function setHost($host)
{
    $this->host = $host;

    return $this;
}

/**
 * Get host
 *
 * @return string 
 */
public function getHost()
{
    return $this->host;
}

/**
 * Set isDefault
 *
 * @param boolean $isDefault
 * @return Site
 */
public function setIsDefault($isDefault)
{
    $this->isDefault = $isDefault;

    return $this;
}

/**
 * Get isDefault
 *
 * @return boolean 
 */
public function getIsDefault()
{
    return $this->isDefault;
}

/**
 * Set enabled
 *
 * @param boolean $enabled
 * @return Site
 */
public function setEnabled($enabled)
{
    $this->enabled = $enabled;

    return $this;
}

/**
 * Get enabled
 *
 * @return boolean 
 */
public function getEnabled()
{
    return $this->enabled;
}

/**
 * Set analytics
 *
 * @param string $analytics
 * @return Site
 */
public function setAnalytics($analytics)
{
    $this->analytics = $analytics;

    return $this;
}

/**
 * Get analytics
 *
 * @return string 
 */
public function getAnalytics()
{
    return $this->analytics;
}    

/**
 * Get ID from Filter
 *
 * @return string 
 */    
public function getIdByFilter()
{
    return $this->idByFilter;
}       

/**
 * Get name from Filter
 *
 * @return string 
 */    
public function getNameByFilter()
{
    return $this->nameByFilter;
} 

/**
 * Set image
 *
 * @param string $image
 * @return Site
 */
public function setImage($image)
{
    $this->image = $image;

    return $this;
}

/**
 * Get image
 *
 * @return string 
 */
public function getImage()
{
    return $this->image;
}

 /**
 * Set file
 *
 * @param string $file
 * @return Site
 */
public function setFile($file)
{
    $this->file = $file;

    return $this;
}

/**
 * Get file
 *
 * @return string 
 */
public function getFile()
{
    return $this->file;
} 


/**
 * 
 * Tree functions
 */
public function setParent(Site $parent = null)
{
    $this->parent = $parent;
}

public function getParent()
{
    return $this->parent;
}

public function getRoot()
{
    return $this->root;
}

public function getLvl()
{
    return $this->lvl;
}

public function getChildren()
{
    return $this->children;
}

public function getLft()
{
    return $this->lft;
}

public function getRgt()
{
    return $this->rgt;
}

/**
 * Add a method to the entity class that shows the name indented by nesting level
 */
public function getLeveledName()
{
    return str_repeat(
        html_entity_decode('&nbsp;', ENT_QUOTES, 'UTF-8'),
        ($this->getLvl()) * 3
    ) . $this->getName();
}
public function getLeveledPosition()
{
    return str_repeat(
        html_entity_decode('&nbsp;', ENT_QUOTES, 'UTF-8'),
        ($this->getLvl()) * 3
    );
}  
}

And here is SiteTranslation entity:

namespace Pnet\ConlocoBundle\Entity;

use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;

/**
 * @ORM\Entity
 */
class SiteTranslation
{
use ORMBehaviors\Translatable\Translation;

/**
* @ORM\Column(type="string", length=60)
* @Assert\NotBlank(message="site.name.notBlank", groups={"edit"})
* @Assert\Length(max = "60", maxMessage = "site.name.maxLength", groups={"edit"})
*/ 
protected $name; 

/**
 * @ORM\Column(type="string", length=100)
 * @Assert\NotBlank(message="site.title.notBlank", groups={"edit"})
 * @Assert\Length(max = "100", maxMessage = "site.title.maxLength", groups={"edit"})
 */     
protected  $title;  

/**
 * @ORM\Column(type="string", length=200)
 * @Assert\NotBlank(message="site.longTitle.notBlank", groups={"edit"})
 * @Assert\Length(max = "200", maxMessage = "site.longTitle.maxLength", groups={"edit"})
 */ 
protected  $longTitle;

/**
 * @ORM\Column(type="string", length=250, nullable=true)
 * @Assert\Length(max = "250", maxMessage = "site.keywords.maxLength", groups={"edit"})
 */     
protected  $keywords;

/**
 * @ORM\Column(type="string", length=500, nullable=true)
 * @Assert\Length(max = "500", maxMessage = "site.description.maxLength", groups={"edit"})
 */       
protected  $description;


/**
 * Set name
 *
 * @param string $name
 * @return Site
 */
public function setName($name)
{
    $this->name = $name;

    return $this;
}

/**
 * Get name
 *
 * @return string 
 */
public function getName()
{
    return $this->name;
}


/**
 * Set title
 *
 * @param string $title
 * @return Site
 */
public function setTitle($title)
{
    $this->title = $title;

    return $this;
}

/**
 * Get title
 *
 * @return string 
 */
public function getTitle()
{
    return $this->title;
}

/**
 * Set longTitle
 *
 * @param string $longTitle
 * @return Site
 */
public function setLongTitle($longTitle)
{
    $this->longTitle = $longTitle;

    return $this;
}

/**
 * Get longTitle
 *
 * @return string 
 */
public function getLongTitle()
{
    return $this->longTitle;
}

/**
 * Set keywords
 *
 * @param string $keywords
 * @return Site
 */
public function setKeywords($keywords)
{
    $this->keywords = $keywords;

    return $this;
}

/**
 * Get keywords
 *
 * @return string 
 */
public function getKeywords()
{
    return $this->keywords;
}

/**
 * Set description
 *
 * @param string $description
 * @return Site
 */
public function setDescription($description)
{
    $this->description = $description;

    return $this;
}

/**
 * Get description
 *
 * @return string 
 */
public function getDescription()
{
    return $this->description;
}
}
A.L
  • 10,259
  • 10
  • 67
  • 98
kvaje
  • 91
  • 1
  • 8

3 Answers3

5

TL;DR:

As a (probably dirty) workaround, use

{{ item.getName }}

in your Twig template instead of

{{ item.name }}

Explanation:

I came across the same issue and i think that this should be considered a bug in the Knp DoctrineBehaviors documentation when used with Twig. When you call this in your Twig template :

{{ item.name }}

This is what Twig does behind the scenes to get the name property :

  1. try to get the name public property of the item object
  2. if not found, checks for the name public method of the item object
  3. if not found, checks for the getName() public method of the "item" object
  4. if not found, checks for the __call() magic method (and calls it with the name parameter)

The problem here is step 4. The magic __call() method that you defined (as recommended by the official DoctrineBehaviors documentation) is called with the name parameter instead of getName. It then calls the proxyCurrentLocaleTranslation() method who tries to call the name public method of your translation class. Of course, it doesn't exist because you only have a getName() method.

See this issue in Twig : https://github.com/twigphp/Twig/issues/342

By using directly the {{ item.getName }} code in Twig, the proper method name will be called.

A.L
  • 10,259
  • 10
  • 67
  • 98
Derek
  • 1,826
  • 18
  • 25
2

This work for me:

public function __call($method, $arguments)
{
    try {
        return $this->proxyCurrentLocaleTranslation($method, $arguments);
    } catch (\Symfony\Component\Debug\Exception\ContextErrorException $e) {
        return $this->proxyCurrentLocaleTranslation('get' . ucfirst($method), $arguments);
    }

}
Kleinast
  • 53
  • 7
0

You haven't moved all the translatable properties/methods to your <Name>Translation class.

The exception clearly states that there is no name/getName method in your SiteTranslation class.

Please read my answer over here to see how Knp\DoctrineBehaviors's magic translation proxy is used correctly.

Community
  • 1
  • 1
Nicolai Fröhlich
  • 51,330
  • 11
  • 126
  • 130
  • In Site Entity I don't have translatable properties/methods. I have moved them all to SiteTranslation. And in SiteTranslation I have protected $name;, public function setName($name) and public function getName(). In Site Entity I have __call: return $this->proxyCurrentLocaleTranslation($method, $arguments); – kvaje Feb 17 '14 at 21:07
  • can you please try changing the visibility of the `$name` property in the translation class from `protected` to `public` and report back ? – Nicolai Fröhlich Feb 18 '14 at 00:41
  • I changed to public `$name` but still don't work. Error message is the same. – kvaje Feb 18 '14 at 12:05