Skip to content

Commit 235770c

Browse files
authored
Merge pull request #1730 from malarzm/xml-mapping-dumper
Converter to XML for mapping
2 parents a098025 + 6255cb5 commit 235770c

File tree

1 file changed

+388
-0
lines changed

1 file changed

+388
-0
lines changed
Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
<?php
2+
3+
namespace Doctrine\ODM\MongoDB\Tools\Console\Command;
4+
5+
use Doctrine\Common\Util\Inflector;
6+
use Doctrine\ODM\MongoDB\DocumentManager;
7+
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
8+
use Doctrine\ODM\MongoDB\Tools\Console\MetadataFilter;
9+
use Symfony\Component\Console\Command\Command;
10+
use Symfony\Component\Console\Input\InputArgument;
11+
use Symfony\Component\Console\Input\InputInterface;
12+
use Symfony\Component\Console\Input\InputOption;
13+
use Symfony\Component\Console\Output\OutputInterface;
14+
15+
/**
16+
* @deprecated class was deprecated in 1.3 and will be removed in 2.0.
17+
*/
18+
class ConvertMappingToXmlCommand extends Command
19+
{
20+
protected function configure()
21+
{
22+
$this
23+
->setName('odm:mapping:dump-xml')
24+
->setDescription('Converts mappings to the XML format. Available in 1.3 only.')
25+
->addArgument('dest-path', InputArgument::REQUIRED, 'The path to generate your XML mappings.')
26+
->addOption('filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match documents that should be processed.')
27+
->addOption('ext', null, InputOption::VALUE_OPTIONAL, 'Extension for created files.', 'dcm.xml')
28+
;
29+
}
30+
31+
protected function execute(InputInterface $input, OutputInterface $output)
32+
{
33+
/** @var DocumentManager $dm */
34+
$dm = $this->getHelper('documentManager')->getDocumentManager();
35+
36+
$metadatas = MetadataFilter::filter(
37+
$dm->getMetadataFactory()->getAllMetadata(),
38+
$input->getOption('filter')
39+
);
40+
$destPath = rtrim($input->getArgument('dest-path'), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
41+
42+
if (! count($metadatas)) {
43+
$output->writeln('No Metadata Classes to process.');
44+
return;
45+
}
46+
47+
/** @var ClassMetadataInfo $metadata */
48+
foreach ($metadatas as $metadata) {
49+
$output->writeln(sprintf('Processing document "<info>%s</info>"', $metadata->name));
50+
$this->inspectDeprecations($metadata, $output);
51+
$xml = $this->export($metadata);
52+
$dom = new \DOMDocument('1.0', 'UTF-8');
53+
$dom->loadXML($xml->asXML());
54+
$dom->formatOutput = true;
55+
file_put_contents(
56+
sprintf("%s%s.%s", $destPath, str_replace('\\', '.', $metadata->name), $input->getOption('ext')),
57+
$dom->saveXML()
58+
);
59+
}
60+
61+
$output->writeln(PHP_EOL . sprintf('XML mapping files generated to "<info>%s</info>"', $destPath));
62+
}
63+
64+
private function inspectDeprecations(ClassMetadataInfo $metadata, OutputInterface $output)
65+
{
66+
if (! empty($metadata->requireIndexes)) {
67+
$output->writeln('<comment>!</comment> requireIndexes has been deprecated, omitting.');
68+
}
69+
if (! empty($metadata->slaveOkay)) {
70+
$output->writeln('<comment>!</comment> slaveOkay has been deprecated, omitting. Please specify read preference manually.');
71+
}
72+
foreach ($metadata->indexes as $index) {
73+
if (! empty($index['options']['partialFilterExpression'])) {
74+
$output->writeln('<comment>!</comment> Index\'s partialFilterExpression can not be converted automatically. Please do so manually.');
75+
break;
76+
}
77+
}
78+
}
79+
80+
private function export(ClassMetadataInfo $metadata)
81+
{
82+
$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?>
83+
<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping"
84+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
85+
xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping
86+
http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd" />');
87+
88+
if ($metadata->isEmbeddedDocument) {
89+
$root = $xml->addChild('embedded-document');
90+
} elseif ($metadata->isMappedSuperclass) {
91+
$root = $xml->addChild('mapped-superclass');
92+
} elseif ($metadata->isQueryResultDocument) {
93+
$root = $xml->addChild('query-result-document');
94+
} else {
95+
$root = $xml->addChild('document');
96+
}
97+
$root->addAttribute('name', $metadata->name);
98+
99+
$this->processDocumentLevelConfiguration($metadata, $root);
100+
101+
if (! empty($metadata->lifecycleCallbacks)) {
102+
$callbacks = $root->addChild('lifecycle-callbacks');
103+
foreach ($metadata->lifecycleCallbacks as $event => $methods) {
104+
foreach ($methods as $method) {
105+
$callback = $callbacks->addChild('lifecycle-callback');
106+
$callback->addAttribute('method', $method);
107+
$callback->addAttribute('type', $event);
108+
}
109+
}
110+
}
111+
if (! empty($metadata->alsoLoadMethods)) {
112+
$alsoLoadMethods = $root->addChild('also-load-methods');
113+
foreach ($metadata->alsoLoadMethods as $method => $fields) {
114+
$alsoLoad = $alsoLoadMethods->addChild('also-load-method');
115+
$alsoLoad->addAttribute('method', $method);
116+
$alsoLoad->addAttribute('field', $fields[0]);
117+
}
118+
}
119+
120+
foreach ($metadata->fieldMappings as $mapping) {
121+
if (empty($mapping['association'])) {
122+
if (empty($mapping['id'])) {
123+
$field = $root->addChild('field');
124+
} else {
125+
$field = $root->addChild('id');
126+
}
127+
128+
$this->processField($mapping, $field);
129+
} elseif ($mapping['association'] === ClassMetadataInfo::EMBED_ONE) {
130+
$field = $root->addChild('embed-one');
131+
$this->processEmbed($mapping, $field);
132+
} elseif ($mapping['association'] === ClassMetadataInfo::EMBED_MANY) {
133+
$field = $root->addChild('embed-many');
134+
$this->processEmbed($mapping, $field);
135+
} elseif ($mapping['association'] === ClassMetadataInfo::REFERENCE_ONE) {
136+
$field = $root->addChild('reference-one');
137+
$this->processReference($mapping, $field);
138+
} elseif ($mapping['association'] === ClassMetadataInfo::REFERENCE_MANY) {
139+
$field = $root->addChild('reference-many');
140+
$this->processReference($mapping, $field);
141+
}
142+
}
143+
144+
return $xml;
145+
}
146+
147+
private function processField(array $mapping, \SimpleXMLElement $field)
148+
{
149+
if (isset($mapping['fieldName']) && $mapping['fieldName'] === $mapping['name']) {
150+
unset($mapping['fieldName']);
151+
}
152+
$attributes = [
153+
'field-name', 'type',
154+
];
155+
if (empty($mapping['id'])) {
156+
$attributes += ['name', 'file', 'distance', 'version', 'lock', 'nullable', 'not-saved'];
157+
}
158+
if (isset($mapping['strategy']) && $mapping['strategy'] !== 'set') {
159+
$attributes[] = 'strategy';
160+
}
161+
// indexes from fields will be moved with indexes in general, no need to do it here
162+
$this->copyAttributes($attributes, $mapping, $field);
163+
if (! empty($mapping['alsoLoadFields'])) {
164+
$field->addAttribute('also-load', join(',', $mapping['alsoLoadFields']));
165+
}
166+
if (! empty($mapping['id']) && ! empty($mapping['options'])) {
167+
foreach ($mapping['options'] as $name => $value) {
168+
$option = $field->addChild('id-generator-option');
169+
$option->addAttribute('name', $name);
170+
$option->addAttribute('value', $value);
171+
}
172+
}
173+
}
174+
175+
private function processEmbed(array $mapping, \SimpleXMLElement $field)
176+
{
177+
$field->addAttribute('field', $mapping['name']);
178+
if (isset($mapping['fieldName']) && $mapping['fieldName'] === $mapping['name']) {
179+
unset($mapping['fieldName']);
180+
}
181+
$attributes = ['field-name', 'target-document', 'collection-class', 'not-saved'];
182+
if ($mapping['type'] === ClassMetadataInfo::MANY) {
183+
$attributes[] = 'strategy';
184+
}
185+
$this->copyAttributes($attributes, $mapping, $field);
186+
$this->processDiscriminatorSettings($mapping, $field);
187+
if (! empty($mapping['alsoLoadFields'])) {
188+
$field->addAttribute('also-load', join(',', $mapping['alsoLoadFields']));
189+
}
190+
}
191+
192+
private function processReference(array $mapping, \SimpleXMLElement $field)
193+
{
194+
$field->addAttribute('field', $mapping['name']);
195+
if (isset($mapping['fieldName']) && $mapping['fieldName'] === $mapping['name']) {
196+
unset($mapping['fieldName']);
197+
}
198+
$attributes = [
199+
'field-name', 'target-document', 'collection-class', 'store-as', 'inversedBy',
200+
'mapped-by', 'repository-method', 'limit', 'skip', 'orphan-removal', 'not-saved',
201+
];
202+
if ($mapping['type'] === ClassMetadataInfo::MANY) {
203+
$attributes[] = 'strategy';
204+
}
205+
$this->copyAttributes($attributes, $mapping, $field);
206+
$this->processDiscriminatorSettings($mapping, $field);
207+
if (! empty($mapping['alsoLoadFields'])) {
208+
$field->addAttribute('also-load', join(',', $mapping['alsoLoadFields']));
209+
}
210+
if (! empty($mapping['cascade'])) {
211+
$cascade = $field->addChild('cascade');
212+
foreach (count($mapping['cascade']) < 5 ? $mapping['cascade'] : ['all'] as $c) {
213+
$cascade->addChild($c);
214+
}
215+
}
216+
if (! empty($mapping['sort'])) {
217+
$sort = $field->addChild('sort');
218+
foreach ($mapping['sort'] as $f => $order) {
219+
$sortSort = $sort->addChild('sort');
220+
$sortSort->addAttribute('field', $field);
221+
$sortSort->addAttribute('order', $order);
222+
}
223+
}
224+
if (! empty($mapping['criteria'])) {
225+
$criteria = $field->addChild('criteria');
226+
foreach ($mapping['criteria'] as $f => $value) {
227+
$criteriaCriteria = $criteria->addChild('criteria');
228+
$criteriaCriteria->addAttribute('field', $field);
229+
$criteriaCriteria->addAttribute('value', $value);
230+
}
231+
}
232+
if (! empty($mapping['prime'])) {
233+
$prime = $field->addChild('prime');
234+
foreach ($mapping['prime'] as $primed) {
235+
$f = $prime->addChild('field');
236+
$f->addAttribute('name', $primed);
237+
}
238+
}
239+
}
240+
241+
private function processDiscriminatorSettings(array $mapping, \SimpleXMLElement $field)
242+
{
243+
if (! empty($mapping['discriminatorField'])) {
244+
$discriminatorField = $field->addChild('discriminator-field');
245+
$discriminatorField->addAttribute('name', $mapping['discriminatorField']);
246+
}
247+
if (! empty($mapping['discriminatorMap'])) {
248+
$map = $field->addChild('discriminator-map');
249+
foreach ($mapping['discriminatorMap'] as $value => $class) {
250+
$dmapping = $map->addChild('discriminator-mapping');
251+
$dmapping->addAttribute('value', $value);
252+
$dmapping->addAttribute('class', $class);
253+
}
254+
}
255+
if (! empty($mapping['defaultDiscriminatorValue'])) {
256+
$defaultDiscriminator = $field->addChild('default-discriminator-value');
257+
$defaultDiscriminator->addAttribute('value', $mapping['defaultDiscriminatorValue']);
258+
}
259+
}
260+
261+
private function copyAttributes(array $attributes, array $data, \SimpleXMLElement $xml)
262+
{
263+
foreach ($attributes as $attr) {
264+
$camelCase = Inflector::camelize($attr);
265+
if (! isset($data[$camelCase])) {
266+
continue;
267+
}
268+
if (is_bool($data[$camelCase])) {
269+
$xml->addAttribute($attr, $data[$camelCase] ? 'true' : 'false');
270+
} else {
271+
$xml->addAttribute($attr, $data[$camelCase]);
272+
}
273+
}
274+
}
275+
276+
private function processDocumentLevelConfiguration(ClassMetadataInfo $metadata, \SimpleXMLElement $root)
277+
{
278+
// document level configuration
279+
if (! empty($metadata->customRepositoryClassName)) {
280+
$root->addAttribute('repository-class', $metadata->customRepositoryClassName);
281+
}
282+
if (! empty($metadata->db)) {
283+
$root->addAttribute('db', $metadata->db);
284+
}
285+
if (! empty($metadata->collection)) {
286+
$root->addAttribute('collection', $metadata->collection);
287+
}
288+
if (! empty($metadata->collectionCapped)) {
289+
$root->addAttribute('capped-collection', 'true');
290+
if (! empty($metadata->collectionMax)) {
291+
$root->addAttribute('capped-collection-max', $metadata->collectionMax);
292+
}
293+
if (! empty($metadata->collectionSize)) {
294+
$root->addAttribute('capped-collection-size', $metadata->collectionSize);
295+
}
296+
}
297+
if (! empty($metadata->writeConcern)) {
298+
$root->addAttribute('writeConcern', $metadata->writeConcern);
299+
}
300+
if ($metadata->inheritanceType !== ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
301+
$root->addAttribute(
302+
'inheritance-type',
303+
$metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION
304+
? 'SINGLE_COLLECTION' : 'COLLECTION_PER_CLASS'
305+
);
306+
}
307+
if ($metadata->changeTrackingPolicy !== ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT) {
308+
$root->addAttribute(
309+
'change-tracking-policy',
310+
$metadata->changeTrackingPolicy === ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT
311+
? 'DEFERRED_EXPLICIT' : 'NOTIFY'
312+
);
313+
}
314+
if (! empty($metadata->isReadOnly)) {
315+
$root->addAttribute('read-only', 'true');
316+
}
317+
if (! empty($metadata->discriminatorField)) {
318+
$discriminatorField = $root->addChild('discriminator-field');
319+
$discriminatorField->addAttribute('name', $metadata->discriminatorField);
320+
}
321+
if (! empty($metadata->discriminatorMap)) {
322+
$map = $root->addChild('discriminator-map');
323+
foreach ($metadata->discriminatorMap as $value => $class) {
324+
$mapping = $map->addChild('discriminator-mapping');
325+
$mapping->addAttribute('value', $value);
326+
$mapping->addAttribute('class', $class);
327+
}
328+
}
329+
if (! empty($metadata->defaultDiscriminatorValue)) {
330+
$defaultDiscriminator = $root->addChild('default-discriminator-value');
331+
$defaultDiscriminator->addAttribute('value', $metadata->defaultDiscriminatorValue);
332+
}
333+
if (! empty($metadata->shardKey)) {
334+
$shardKey = $root->addChild('shard-key');
335+
foreach ($metadata->shardKey['keys'] as $name => $order) {
336+
$key = $shardKey->addChild('key');
337+
$key->addAttribute('name', $name);
338+
$key->addAttribute('order', $order);
339+
}
340+
foreach ($metadata->shardKey['options'] as $name => $value) {
341+
$option = $shardKey->addChild('option');
342+
$option->addAttribute('name', $name);
343+
if (is_bool($value)) {
344+
$option->addAttribute('value', $value ? 'true' : 'false');
345+
} else {
346+
$option->addAttribute('value', $value);
347+
}
348+
}
349+
}
350+
if (! empty($metadata->readPreference)) {
351+
$readPreference = $root->addChild('read-preference');
352+
$readPreference->addAttribute('mode', $metadata->readPreference);
353+
if (! empty($metadata->readPreferenceTags)) {
354+
foreach ($metadata->readPreferenceTags as $tagSetConf) {
355+
$tagSet = $readPreference->addChild('tag-set');
356+
foreach ($tagSetConf as $name => $value) {
357+
$tag = $tagSet->addChild('tag');
358+
$tag->addAttribute('name', $name);
359+
$tag->addAttribute('value', $value);
360+
}
361+
}
362+
}
363+
}
364+
if (! empty($metadata->indexes)) {
365+
$indexes = $root->addChild('indexes');
366+
foreach ($metadata->indexes as $index) {
367+
$indexTag = $indexes->addChild('index');
368+
foreach ($index['keys'] as $name => $order) {
369+
$key = $indexTag->addChild('key');
370+
$key->addAttribute('name', $name);
371+
$key->addAttribute('order', $order);
372+
}
373+
foreach ($index['options'] as $name => $value) {
374+
if ($name === 'partialFilterExpression') {
375+
continue; // not touching this, needs to be migrated manually
376+
}
377+
$option = $indexTag->addChild('option');
378+
$option->addAttribute('name', $name);
379+
if (is_bool($value)) {
380+
$option->addAttribute('value', $value ? 'true' : 'false');
381+
} else {
382+
$option->addAttribute('value', $value);
383+
}
384+
}
385+
}
386+
}
387+
}
388+
}

0 commit comments

Comments
 (0)