1<?php
2
3namespace JMS\Serializer;
4
5use JMS\Serializer\Accessor\AccessorStrategyInterface;
6use JMS\Serializer\Metadata\ClassMetadata;
7use JMS\Serializer\Metadata\PropertyMetadata;
8use JMS\Serializer\Naming\AdvancedNamingStrategyInterface;
9use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
10use JMS\Serializer\Util\Writer;
11use Symfony\Component\Yaml\Inline;
12
13/**
14 * Serialization Visitor for the YAML format.
15 *
16 * @see http://www.yaml.org/spec/
17 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
18 */
19class YamlSerializationVisitor extends AbstractVisitor
20{
21    public $writer;
22
23    private $navigator;
24    private $stack;
25    private $metadataStack;
26    private $currentMetadata;
27
28    public function __construct($namingStrategy, AccessorStrategyInterface $accessorStrategy = null)
29    {
30        parent::__construct($namingStrategy, $accessorStrategy);
31
32        $this->writer = new Writer();
33    }
34
35    public function setNavigator(GraphNavigator $navigator)
36    {
37        $this->navigator = $navigator;
38        $this->writer->reset();
39        $this->stack = new \SplStack;
40        $this->metadataStack = new \SplStack;
41    }
42
43    public function visitNull($data, array $type, Context $context)
44    {
45        if ('' === $this->writer->content) {
46            $this->writer->writeln('null');
47        }
48
49        return 'null';
50    }
51
52    public function visitString($data, array $type, Context $context)
53    {
54        $v = Inline::dump($data);
55
56        if ('' === $this->writer->content) {
57            $this->writer->writeln($v);
58        }
59
60        return $v;
61    }
62
63    /**
64     * @param array $data
65     * @param array $type
66     */
67    public function visitArray($data, array $type, Context $context)
68    {
69        $isHash = isset($type['params'][1]);
70
71        $count = $this->writer->changeCount;
72        $isList = (isset($type['params'][0]) && !isset($type['params'][1]))
73            || array_keys($data) === range(0, \count($data) - 1);
74
75        foreach ($data as $k => $v) {
76            if (null === $v && $context->shouldSerializeNull() !== true) {
77                continue;
78            }
79
80            if ($isList && !$isHash) {
81                $this->writer->writeln('-');
82            } else {
83                $this->writer->writeln(Inline::dump($k) . ':');
84            }
85
86            $this->writer->indent();
87
88            if (null !== $v = $this->navigator->accept($v, $this->getElementType($type), $context)) {
89                $this->writer
90                    ->rtrim(false)
91                    ->writeln(' ' . $v);
92            }
93
94            $this->writer->outdent();
95        }
96
97        if ($count === $this->writer->changeCount && isset($type['params'][1])) {
98            $this->writer
99                ->rtrim(false)
100                ->writeln(' {}');
101        } elseif (empty($data)) {
102            $this->writer
103                ->rtrim(false)
104                ->writeln(' []');
105        }
106    }
107
108    public function visitBoolean($data, array $type, Context $context)
109    {
110        $v = $data ? 'true' : 'false';
111
112        if ('' === $this->writer->content) {
113            $this->writer->writeln($v);
114        }
115
116        return $v;
117    }
118
119    public function visitDouble($data, array $type, Context $context)
120    {
121        $v = (string)$data;
122
123        if ('' === $this->writer->content) {
124            $this->writer->writeln($v);
125        }
126
127        return $v;
128    }
129
130    public function visitInteger($data, array $type, Context $context)
131    {
132        $v = (string)$data;
133
134        if ('' === $this->writer->content) {
135            $this->writer->writeln($v);
136        }
137
138        return $v;
139    }
140
141    public function startVisitingObject(ClassMetadata $metadata, $data, array $type, Context $context)
142    {
143    }
144
145    public function visitProperty(PropertyMetadata $metadata, $data, Context $context)
146    {
147        $v = $this->accessor->getValue($data, $metadata);
148
149        if ((null === $v && $context->shouldSerializeNull() !== true)
150            || (true === $metadata->skipWhenEmpty && ($v instanceof \ArrayObject || \is_array($v)) && 0 === count($v))
151        ) {
152            return;
153        }
154
155        if ($this->namingStrategy instanceof AdvancedNamingStrategyInterface) {
156            $name = $this->namingStrategy->getPropertyName($metadata, $context);
157        } else {
158            $name = $this->namingStrategy->translateName($metadata);
159        }
160
161        if (!$metadata->inline) {
162            $this->writer
163                ->writeln(Inline::dump($name) . ':')
164                ->indent();
165        }
166
167        $this->setCurrentMetadata($metadata);
168
169        $count = $this->writer->changeCount;
170
171        if (null !== $v = $this->navigator->accept($v, $metadata->type, $context)) {
172            $this->writer
173                ->rtrim(false)
174                ->writeln(' ' . $v);
175        } elseif ($count === $this->writer->changeCount && !$metadata->inline) {
176            $this->writer->revert();
177        }
178
179        if (!$metadata->inline) {
180            $this->writer->outdent();
181        }
182        $this->revertCurrentMetadata();
183    }
184
185    public function endVisitingObject(ClassMetadata $metadata, $data, array $type, Context $context)
186    {
187    }
188
189    public function setCurrentMetadata(PropertyMetadata $metadata)
190    {
191        $this->metadataStack->push($this->currentMetadata);
192        $this->currentMetadata = $metadata;
193    }
194
195    public function revertCurrentMetadata()
196    {
197        return $this->currentMetadata = $this->metadataStack->pop();
198    }
199
200    public function getNavigator()
201    {
202        return $this->navigator;
203    }
204
205    public function getResult()
206    {
207        return $this->writer->getContent();
208    }
209}
210