Skip to content

Commit ce1151b

Browse files
committed
ValidationContext extractor
1 parent 7382bd2 commit ce1151b

File tree

3 files changed

+316
-0
lines changed

3 files changed

+316
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace JMS\TranslationBundle\Tests\Translation\Extractor\File\Fixture;
4+
5+
use Symfony\Component\Validator\Context\ExecutionContext;
6+
7+
/**
8+
* Class MyEntity
9+
*/
10+
class MyEntity
11+
{
12+
public function validateConstraintWithDefaultDomain(ExecutionContext $context)
13+
{
14+
$context
15+
->buildViolation('entity.default')
16+
->addViolation();
17+
}
18+
19+
public function validateConstraintWithCustomDomain(ExecutionContext $context)
20+
{
21+
$context
22+
->buildViolation('entity.custom-domain')
23+
->setTranslationDomain('custom-domain')
24+
->addViolation();
25+
}
26+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace JMS\TranslationBundle\Tests\Translation\Extractor\File;
4+
5+
use JMS\TranslationBundle\Model\Message;
6+
use JMS\TranslationBundle\Model\MessageCatalogue;
7+
use JMS\TranslationBundle\Translation\Extractor\File\ValidationContextExtractor;
8+
9+
class ValidationContextExtractorTest extends BasePhpFileExtractorTest
10+
{
11+
public function testExtractValidationMessages()
12+
{
13+
$fileSourceFactory = $this->getFileSourceFactory();
14+
$fixtureSplInfo = new \SplFileInfo(__DIR__.'/Fixture/MyEntity.php');
15+
16+
17+
$expected = new MessageCatalogue();
18+
19+
$message = new Message('entity.default');
20+
$message->addSource($fileSourceFactory->create($fixtureSplInfo, 15));
21+
$expected->add($message);
22+
23+
$message = new Message('entity.custom-domain', 'custom-domain');
24+
$message->addSource($fileSourceFactory->create($fixtureSplInfo, 22));
25+
$expected->add($message);
26+
27+
$this->assertEquals($expected, $this->extract('MyEntity.php'));
28+
}
29+
30+
protected function getDefaultExtractor()
31+
{
32+
return new ValidationContextExtractor($this->getFileSourceFactory());
33+
}
34+
}
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
<?php
2+
3+
/*
4+
* Copyright 2016 Arturs Vonda <[email protected]>
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
namespace JMS\TranslationBundle\Translation\Extractor\File;
20+
21+
use JMS\TranslationBundle\Model\FileSource;
22+
use JMS\TranslationBundle\Model\Message;
23+
use JMS\TranslationBundle\Model\MessageCatalogue;
24+
use JMS\TranslationBundle\Model\SourceInterface;
25+
use JMS\TranslationBundle\Translation\Extractor\FileVisitorInterface;
26+
use JMS\TranslationBundle\Translation\FileSourceFactory;
27+
use PhpParser\Node;
28+
use PhpParser\NodeTraverser;
29+
use PhpParser\NodeVisitor;
30+
use SplFileInfo;
31+
32+
/**
33+
* Class ValidationContextExtractor
34+
*
35+
* Extracts
36+
*/
37+
class ValidationContextExtractor implements FileVisitorInterface, NodeVisitor
38+
{
39+
/**
40+
* @var NodeTraverser
41+
*/
42+
private $traverser;
43+
/**
44+
* @var array
45+
*/
46+
private $messages = [];
47+
/**
48+
* @var MessageCatalogue
49+
*/
50+
private $catalogue;
51+
/**
52+
* @var SplFileInfo
53+
*/
54+
private $file;
55+
/**
56+
* @var array
57+
*/
58+
private $aliases = [];
59+
/**
60+
* @var string
61+
*/
62+
private $contextVariable;
63+
/**
64+
* @var string|null
65+
*/
66+
private $domain;
67+
/**
68+
* @var string|null
69+
*/
70+
private $id;
71+
/**
72+
* @var FileSource|null
73+
*/
74+
private $source;
75+
private $fileSourceFactory;
76+
77+
/**
78+
* ValidationContextExtractor constructor.
79+
*
80+
* @param FileSourceFactory $fileSourceFactory
81+
*/
82+
public function __construct(FileSourceFactory $fileSourceFactory)
83+
{
84+
$this->fileSourceFactory = $fileSourceFactory;
85+
$this->traverser = new NodeTraverser();
86+
$this->traverser->addVisitor($this);
87+
}
88+
89+
/**
90+
* {@inheritdoc}
91+
*/
92+
public function visitFile(SplFileInfo $file, MessageCatalogue $catalogue)
93+
{
94+
}
95+
96+
/**
97+
* {@inheritdoc}
98+
*/
99+
public function visitPhpFile(SplFileInfo $file, MessageCatalogue $catalogue, array $ast)
100+
{
101+
$this->file = $file;
102+
$this->catalogue = $catalogue;
103+
$this->messages = [];
104+
$this->traverser->traverse($ast);
105+
106+
foreach ($this->messages as $message) {
107+
$this->addToCatalogue($message['id'], $message['source'], $message['domain']);
108+
}
109+
}
110+
111+
/**
112+
* {@inheritdoc}
113+
*/
114+
public function visitTwigFile(SplFileInfo $file, MessageCatalogue $catalogue, \Twig_Node $ast)
115+
{
116+
}
117+
118+
/**
119+
* {@inheritdoc}
120+
*/
121+
public function beforeTraverse(array $nodes)
122+
{
123+
}
124+
125+
/**
126+
* {@inheritdoc}
127+
*/
128+
public function enterNode(Node $node)
129+
{
130+
if ($node instanceof Node\Stmt\Namespace_) {
131+
$this->aliases = [];
132+
133+
return;
134+
}
135+
136+
if ($node instanceof Node\Stmt\Use_) {
137+
foreach ($node->uses as $use) {
138+
$this->aliases[$use->alias] = (string) $use->name;
139+
}
140+
141+
return;
142+
}
143+
144+
if ($node instanceof Node\Stmt\ClassMethod) {
145+
$params = $node->getParams();
146+
if (!count($params)) {
147+
return;
148+
}
149+
$param1 = $params[0];
150+
$paramClass = $this->resolveAlias((string) $param1->type);
151+
if (is_subclass_of($paramClass, '\Symfony\Component\Validator\Context\ExecutionContextInterface')) {
152+
$this->contextVariable = $param1->name;
153+
}
154+
155+
return;
156+
}
157+
158+
if ($node instanceof Node\Expr\MethodCall) {
159+
$this->parseMethodCall($node);
160+
}
161+
}
162+
163+
/**
164+
* @param Node\Expr\MethodCall $node
165+
*/
166+
private function parseMethodCall(Node\Expr\MethodCall $node)
167+
{
168+
if (!$this->contextVariable) {
169+
return;
170+
}
171+
172+
if ($node->var instanceof Node\Expr\MethodCall) {
173+
$this->parseMethodCall($node->var);
174+
}
175+
176+
if ($node->name === 'buildViolation') {
177+
$this->id = null;
178+
$this->domain = null;
179+
180+
if ($node->args) {
181+
$arg1 = $node->args[0];
182+
if ($arg1->value instanceof Node\Scalar\String_) {
183+
$this->id = $arg1->value->value;
184+
$this->source = $this->fileSourceFactory->create($this->file, $arg1->value->getLine());
185+
}
186+
}
187+
} elseif ($node->name === 'setTranslationDomain') {
188+
if ($node->args) {
189+
$arg1 = $node->args[0];
190+
if ($arg1->value instanceof Node\Scalar\String_) {
191+
$this->domain = $arg1->value->value;
192+
}
193+
}
194+
} elseif ($node->name === 'addViolation') {
195+
if ($this->id and $this->source) {
196+
$this->messages[] = [
197+
'id' => $this->id,
198+
'source' => $this->source,
199+
'domain' => $this->domain,
200+
];
201+
}
202+
203+
$this->id = null;
204+
$this->domain = null;
205+
$this->source = null;
206+
}
207+
}
208+
209+
/**
210+
* {@inheritdoc}
211+
*/
212+
public function leaveNode(Node $node)
213+
{
214+
if ($node instanceof Node\Stmt\ClassMethod) {
215+
$this->contextVariable = null;
216+
$this->domain = null;
217+
$this->id = null;
218+
$this->source = null;
219+
}
220+
}
221+
222+
/**
223+
* {@inheritdoc}
224+
*/
225+
public function afterTraverse(array $nodes)
226+
{
227+
}
228+
229+
/**
230+
* @param string $id
231+
* @param SourceInterface $source
232+
* @param string|null $domain
233+
*/
234+
private function addToCatalogue($id, SourceInterface $source, $domain = null)
235+
{
236+
if (null === $domain) {
237+
$message = new Message($id);
238+
} else {
239+
$message = new Message($id, $domain);
240+
}
241+
242+
$message->addSource($source);
243+
244+
$this->catalogue->add($message);
245+
}
246+
247+
/**
248+
* @param $class
249+
*
250+
* @return string
251+
*/
252+
private function resolveAlias($class)
253+
{
254+
return isset($this->aliases[$class]) ? $this->aliases[$class] : $class;
255+
}
256+
}

0 commit comments

Comments
 (0)