1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace LaravelUi5\OData\Service\Cache;
6:
7: use Closure;
8: use LaravelUi5\OData\Edm\Contracts\Container\EntityContainerInterface;
9: use LaravelUi5\OData\Edm\Contracts\Container\EntitySetInterface;
10: use LaravelUi5\OData\Edm\Contracts\Container\FunctionImportInterface;
11: use LaravelUi5\OData\Edm\Contracts\Container\NavigationPropertyBindingInterface;
12: use LaravelUi5\OData\Edm\EdmPrimitiveType;
13: use LaravelUi5\OData\Edm\Contracts\Container\SingletonInterface;
14: use LaravelUi5\OData\Edm\Contracts\EdmxInterface;
15: use LaravelUi5\OData\Edm\Contracts\FunctionInterface;
16: use LaravelUi5\OData\Edm\Contracts\FunctionParameterInterface;
17: use LaravelUi5\OData\Edm\Contracts\Property\NavigationPropertyInterface;
18: use LaravelUi5\OData\Edm\Contracts\Property\PropertyInterface;
19: use LaravelUi5\OData\Edm\Contracts\SchemaInterface;
20: use LaravelUi5\OData\Edm\Contracts\Type\ComplexTypeInterface;
21: use LaravelUi5\OData\Edm\Contracts\Type\EntityTypeInterface;
22: use LaravelUi5\OData\Edm\Contracts\Type\TypeInterface;
23:
24: /**
25: * Generates PHP readonly classes from an EdmxInterface object graph.
26: *
27: * Output is placed in an Edm/ directory with subdirectories:
28: * Types/ — EntityType and ComplexType classes
29: * Entities/ — EntitySet classes
30: * Enums/ — EnumType classes (future)
31: *
32: * Each generated class implements the corresponding Edm\Contracts\ interface.
33: * The root Edmx.php constructs the full object graph in its constructor.
34: *
35: * Usage:
36: * $writer = new EdmxWriter($edmx, '/path/to/service/Edm', 'App\\OData\\Edm');
37: * $writer->write();
38: */
39: final class EdmxWriter
40: {
41: public function __construct(
42: private readonly EdmxInterface $edmx,
43: private readonly string $outputDir,
44: private readonly string $namespace,
45: private readonly ?Closure $output = null,
46: ) {}
47:
48: public function write(): void
49: {
50: $this->ensureDir($this->outputDir);
51: $this->ensureDir($this->outputDir . '/Types');
52: $this->ensureDir($this->outputDir . '/Entities');
53:
54: // Collect all entity type class names for cross-referencing
55: $typeMap = $this->buildTypeMap();
56:
57: // Write entity types
58: foreach ($this->allEntityTypes() as $type) {
59: $this->writeEntityType($type, $typeMap);
60: }
61:
62: // Write complex types
63: foreach ($this->allComplexTypes() as $type) {
64: $this->writeComplexType($type, $typeMap);
65: }
66:
67: // Write entity sets
68: $container = $this->edmx->getEntityContainer();
69: foreach ($container->getEntitySets() as $set) {
70: $this->writeEntitySet($set, $typeMap);
71: }
72:
73: // Write the root Edmx.php that wires everything together
74: $this->writeEdmx($typeMap);
75:
76: $this->emit('Cache written to ' . $this->outputDir);
77: }
78:
79: // ── Type map ────────────────────────────────────────────────────────────
80:
81: /**
82: * Build a map from qualified type name → generated class name.
83: *
84: * @return array<string, string> qualifiedName → short class name
85: */
86: private function buildTypeMap(): array
87: {
88: $map = [];
89:
90: foreach ($this->allEntityTypes() as $type) {
91: $map[$type->getQualifiedName()] = $type->getName();
92: }
93:
94: foreach ($this->allComplexTypes() as $type) {
95: $map[$type->getQualifiedName()] = $type->getName();
96: }
97:
98: return $map;
99: }
100:
101: // ── Entity type generation ──────────────────────────────────────────────
102:
103: private function writeEntityType(EntityTypeInterface $type, array $typeMap): void
104: {
105: $className = $type->getName();
106: $ns = $this->namespace . '\\Types';
107:
108: $props = $this->generateProperties($type->getDeclaredProperties(), $typeMap);
109: $navProps = $this->generateNavigationProperties($type->getDeclaredNavigationProperties(), $typeMap);
110: $key = $this->generateKeyReferences($type->getKey());
111:
112: $code = <<<PHP
113: <?php
114:
115: declare(strict_types=1);
116:
117: namespace {$ns};
118:
119: use LaravelUi5\OData\Edm\EdmPrimitiveType;
120: use LaravelUi5\OData\Edm\Contracts\Property\NavigationPropertyInterface;
121: use LaravelUi5\OData\Edm\Contracts\Property\PropertyInterface;
122: use LaravelUi5\OData\Edm\Contracts\Type\EntityTypeInterface;
123: use LaravelUi5\OData\Edm\HasAnnotations;
124: use LaravelUi5\OData\Edm\Property\NavigationProperty;
125: use LaravelUi5\OData\Edm\Property\Property;
126: use LaravelUi5\OData\Edm\Type\PrimitiveType;
127:
128: final class {$className} implements EntityTypeInterface
129: {
130: use HasAnnotations;
131:
132: private static ?self \$instance = null;
133:
134: /** @var list<PropertyInterface> */
135: private array \$key;
136:
137: /** @var list<PropertyInterface> */
138: private array \$declaredProperties;
139:
140: /** @var list<NavigationPropertyInterface> */
141: private array \$declaredNavigationProperties;
142:
143: private bool \$initialized = false;
144:
145: public function __construct()
146: {
147: \$this->annotations = [];
148: {$props}
149: \$this->declaredNavigationProperties = [];
150: \$this->key = [{$key}];
151: }
152:
153: public static function instance(): self
154: {
155: return self::\$instance ??= new self();
156: }
157:
158: /** @internal Called by Edmx after all types are instantiated to break circular refs. */
159: public function initNavigationProperties(): void
160: {
161: if (\$this->initialized) return;
162: \$this->initialized = true;
163: {$navProps}
164: }
165:
166: public function getName(): string { return '{$this->e($type->getName())}'; }
167: public function getQualifiedName(): string { return '{$this->e($type->getQualifiedName())}'; }
168: public function getBaseType(): ?EntityTypeInterface { return null; }
169: public function isAbstract(): bool { return false; }
170: public function isOpen(): bool { return false; }
171: public function hasStream(): bool { return {$this->bool($type->hasStream())}; }
172: public function getKey(): array { return \$this->key; }
173: public function getDeclaredProperties(): array { return \$this->declaredProperties; }
174:
175: public function getProperty(string \$name): ?PropertyInterface
176: {
177: foreach (\$this->declaredProperties as \$p) {
178: if (\$p->getName() === \$name) return \$p;
179: }
180: return null;
181: }
182:
183: public function getDeclaredNavigationProperties(): array { return \$this->declaredNavigationProperties; }
184:
185: public function getNavigationProperty(string \$name): ?NavigationPropertyInterface
186: {
187: foreach (\$this->declaredNavigationProperties as \$p) {
188: if (\$p->getName() === \$name) return \$p;
189: }
190: return null;
191: }
192:
193: public function getAnnotations(): array { return \$this->annotations; }
194: public function getAnnotation(string \$term, ?string \$qualifier = null): ?\\LaravelUi5\\OData\\Edm\\Contracts\\Annotation\\AnnotationInterface { return null; }
195: }
196:
197: PHP;
198:
199: $this->writeFile($this->outputDir . '/Types/' . $className . '.php', $this->dedent($code));
200: $this->emit(" Types/{$className}.php");
201: }
202:
203: // ── Complex type generation ─────────────────────────────────────────────
204:
205: private function writeComplexType(ComplexTypeInterface $type, array $typeMap): void
206: {
207: $className = $type->getName();
208: $ns = $this->namespace . '\\Types';
209:
210: $props = $this->generateProperties($type->getDeclaredProperties(), $typeMap);
211:
212: $code = <<<PHP
213: <?php
214:
215: declare(strict_types=1);
216:
217: namespace {$ns};
218:
219: use LaravelUi5\OData\Edm\EdmPrimitiveType;
220: use LaravelUi5\OData\Edm\Contracts\Property\NavigationPropertyInterface;
221: use LaravelUi5\OData\Edm\Contracts\Property\PropertyInterface;
222: use LaravelUi5\OData\Edm\Contracts\Type\ComplexTypeInterface;
223: use LaravelUi5\OData\Edm\HasAnnotations;
224: use LaravelUi5\OData\Edm\Property\Property;
225: use LaravelUi5\OData\Edm\Type\PrimitiveType;
226:
227: final readonly class {$className} implements ComplexTypeInterface
228: {
229: use HasAnnotations;
230:
231: /** @var list<PropertyInterface> */
232: private array \$declaredProperties;
233:
234: public function __construct()
235: {
236: \$this->annotations = [];
237: {$props}
238: }
239:
240: public function getName(): string { return '{$this->e($type->getName())}'; }
241: public function getQualifiedName(): string { return '{$this->e($type->getQualifiedName())}'; }
242: public function getBaseType(): ?ComplexTypeInterface { return null; }
243: public function isAbstract(): bool { return false; }
244: public function isOpen(): bool { return false; }
245: public function getDeclaredProperties(): array { return \$this->declaredProperties; }
246:
247: public function getProperty(string \$name): ?PropertyInterface
248: {
249: foreach (\$this->declaredProperties as \$p) {
250: if (\$p->getName() === \$name) return \$p;
251: }
252: return null;
253: }
254:
255: public function getDeclaredNavigationProperties(): array { return []; }
256: public function getNavigationProperty(string \$name): ?NavigationPropertyInterface { return null; }
257: public function getAnnotations(): array { return \$this->annotations; }
258: public function getAnnotation(string \$term, ?string \$qualifier = null): ?\\LaravelUi5\\OData\\Edm\\Contracts\\Annotation\\AnnotationInterface { return null; }
259: }
260:
261: PHP;
262:
263: $this->writeFile($this->outputDir . '/Types/' . $className . '.php', $this->dedent($code));
264: $this->emit(" Types/{$className}.php");
265: }
266:
267: // ── Entity set generation ───────────────────────────────────────────────
268:
269: private function writeEntitySet(EntitySetInterface $set, array $typeMap): void
270: {
271: $className = $set->getName();
272: $ns = $this->namespace . '\\Entities';
273: $typeClass = $typeMap[$set->getEntityType()->getQualifiedName()] ?? $set->getEntityType()->getName();
274: $typeFqcn = $this->namespace . '\\Types\\' . $typeClass;
275:
276: $bindings = $this->generateBindings($set->getNavigationPropertyBindings());
277:
278: $code = <<<PHP
279: <?php
280:
281: declare(strict_types=1);
282:
283: namespace {$ns};
284:
285: use LaravelUi5\OData\Edm\Container\NavigationPropertyBinding;
286: use LaravelUi5\OData\Edm\Contracts\Container\EntitySetInterface;
287: use LaravelUi5\OData\Edm\Contracts\Container\NavigationPropertyBindingInterface;
288: use LaravelUi5\OData\Edm\Contracts\Type\EntityTypeInterface;
289: use LaravelUi5\OData\Edm\HasAnnotations;
290:
291: final readonly class {$className} implements EntitySetInterface
292: {
293: use HasAnnotations;
294:
295: private EntityTypeInterface \$entityType;
296:
297: /** @var list<NavigationPropertyBindingInterface> */
298: private array \$navigationPropertyBindings;
299:
300: public function __construct()
301: {
302: \$this->annotations = [];
303: \$this->entityType = \\{$typeFqcn}::instance();
304: {$bindings}
305: }
306:
307: public function getName(): string { return '{$this->e($set->getName())}'; }
308: public function getEntityType(): EntityTypeInterface { return \$this->entityType; }
309: public function isIncludedInServiceDocument(): bool { return {$this->bool($set->isIncludedInServiceDocument())}; }
310: public function getNavigationPropertyBindings(): array { return \$this->navigationPropertyBindings; }
311:
312: public function getNavigationPropertyBinding(string \$path): ?NavigationPropertyBindingInterface
313: {
314: foreach (\$this->navigationPropertyBindings as \$b) {
315: if (\$b->getPath() === \$path) return \$b;
316: }
317: return null;
318: }
319:
320: public function getAnnotations(): array { return \$this->annotations; }
321: public function getAnnotation(string \$term, ?string \$qualifier = null): ?\\LaravelUi5\\OData\\Edm\\Contracts\\Annotation\\AnnotationInterface { return null; }
322: }
323:
324: PHP;
325:
326: $this->writeFile($this->outputDir . '/Entities/' . $className . '.php', $this->dedent($code));
327: $this->emit(" Entities/{$className}.php");
328: }
329:
330: // ── Root Edmx generation ────────────────────────────────────────────────
331:
332: private function writeEdmx(array $typeMap): void
333: {
334: $container = $this->edmx->getEntityContainer();
335: $schema = array_values($this->edmx->getSchemas())[0] ?? null;
336: $schemaNamespace = $schema?->getNamespace() ?? '';
337: $schemaAlias = $schema?->getAlias();
338:
339: // Build entity set instantiations
340: $setInits = [];
341: foreach ($container->getEntitySets() as $set) {
342: $setClass = $this->namespace . '\\Entities\\' . $set->getName();
343: $setInits[] = " new \\{$setClass}(),";
344: }
345:
346: // Build singleton instantiations
347: $singletonInits = [];
348: foreach ($container->getSingletons() as $singleton) {
349: $typeClass = $typeMap[$singleton->getEntityType()->getQualifiedName()] ?? $singleton->getEntityType()->getName();
350: $typeFqcn = $this->namespace . '\\Types\\' . $typeClass;
351: $singletonInits[] = " new \\LaravelUi5\\OData\\Edm\\Container\\Singleton('{$this->e($singleton->getName())}', \\{$typeFqcn}::instance()),";
352: }
353:
354: // Build function import instantiations
355: $funcImportInits = [];
356: foreach ($container->getFunctionImports() as $import) {
357: $funcCode = $this->generateFunctionCode($import->getFunction());
358: $funcImportInits[] = " new \\LaravelUi5\\OData\\Edm\\Container\\FunctionImport('{$this->e($import->getName())}', {$funcCode}),";
359: }
360:
361: // Build entity type instantiations for schema
362: $typeInits = [];
363: foreach ($this->allEntityTypes() as $type) {
364: $typeClass = $this->namespace . '\\Types\\' . $type->getName();
365: $typeInits[] = " \\{$typeClass}::instance(),";
366: }
367:
368: // Build complex type instantiations for schema
369: $complexTypeInits = [];
370: foreach ($this->allComplexTypes() as $type) {
371: $typeClass = $this->namespace . '\\Types\\' . $type->getName();
372: $complexTypeInits[] = " \\{$typeClass}::instance(),";
373: }
374:
375: // Build function instantiations for schema
376: $funcInits = [];
377: if ($schema) {
378: foreach ($schema->getFunctions() as $name => $overloads) {
379: foreach ($overloads as $func) {
380: $funcInits[] = ' ' . $this->generateFunctionCode($func) . ',';
381: }
382: }
383: }
384:
385: // Build initNavigationProperties calls for all entity types
386: $navInitCalls = [];
387: foreach ($this->allEntityTypes() as $type) {
388: $typeClass = $this->namespace . '\\Types\\' . $type->getName();
389: if ($type->getDeclaredNavigationProperties() !== []) {
390: $navInitCalls[] = " \\{$typeClass}::instance()->initNavigationProperties();";
391: }
392: }
393:
394: $ns = $this->namespace;
395: $version = $this->e($this->edmx->getVersion());
396: $containerName = $this->e($container->getName());
397: $aliasArg = $schemaAlias !== null ? "'{$this->e($schemaAlias)}'" : 'null';
398:
399: $setBlock = implode("\n", $setInits);
400: $singletonBlock = implode("\n", $singletonInits);
401: $funcImportBlock = implode("\n", $funcImportInits);
402: $typeBlock = implode("\n", $typeInits);
403: $complexTypeBlock = implode("\n", $complexTypeInits);
404: $funcBlock = implode("\n", $funcInits);
405: $navInitBlock = implode("\n", $navInitCalls);
406:
407: $code = <<<PHP
408: <?php
409:
410: declare(strict_types=1);
411:
412: namespace {$ns};
413:
414: use LaravelUi5\OData\Edm\Container\EntityContainer;
415: use LaravelUi5\OData\Edm\Contracts\Container\EntityContainerInterface;
416: use LaravelUi5\OData\Edm\Contracts\EdmxInterface;
417: use LaravelUi5\OData\Edm\Contracts\SchemaInterface;
418: use LaravelUi5\OData\Edm\Schema;
419:
420: /**
421: * Generated by EdmxWriter. Do not edit.
422: */
423: final readonly class Edmx implements EdmxInterface
424: {
425: private EntityContainerInterface \$container;
426:
427: /** @var array<string, SchemaInterface> */
428: private array \$schemas;
429:
430: public function __construct()
431: {
432: \$this->container = new EntityContainer(
433: name: '{$containerName}',
434: entitySets: [
435: {$setBlock}
436: ],
437: singletons: [
438: {$singletonBlock}
439: ],
440: functionImports: [
441: {$funcImportBlock}
442: ],
443: );
444:
445: \$this->schemas = [
446: '{$this->e($schemaNamespace)}' => new Schema(
447: namespace: '{$this->e($schemaNamespace)}',
448: alias: {$aliasArg},
449: entityTypes: [
450: {$typeBlock}
451: ],
452: complexTypes: [
453: {$complexTypeBlock}
454: ],
455: functions: [
456: {$funcBlock}
457: ],
458: ),
459: ];
460:
461: // Wire navigation properties after all types exist (breaks circular refs).
462: {$navInitBlock}
463: }
464:
465: public function getVersion(): string { return '{$version}'; }
466: public function getReferences(): array { return []; }
467: public function getReference(string \$uri): ?\\LaravelUi5\\OData\\Edm\\Contracts\\ReferenceInterface { return null; }
468: public function getSchemas(): array { return \$this->schemas; }
469: public function getSchema(string \$namespace): ?SchemaInterface { return \$this->schemas[\$namespace] ?? null; }
470: public function getEntityContainer(): EntityContainerInterface { return \$this->container; }
471: }
472:
473: PHP;
474:
475: $this->writeFile($this->outputDir . '/Edmx.php', $this->dedent($code));
476: $this->emit(" Edmx.php");
477: }
478:
479: // ── Code generation helpers ─────────────────────────────────────────────
480:
481: /**
482: * Generate property array assignment for declared properties.
483: *
484: * @param list<PropertyInterface> $properties
485: */
486: private function generateProperties(array $properties, array $typeMap): string
487: {
488: $lines = [];
489: foreach ($properties as $prop) {
490: $typeCode = $this->generateTypeCode($prop->getType(), $typeMap);
491: $lines[] = " new Property('{$this->e($prop->getName())}', {$typeCode}),";
492: }
493:
494: $block = implode("\n", $lines);
495: return " \$this->declaredProperties = [\n{$block}\n ];";
496: }
497:
498: /**
499: * Generate navigation property array assignment.
500: *
501: * @param list<NavigationPropertyInterface> $navProps
502: */
503: private function generateNavigationProperties(array $navProps, array $typeMap): string
504: {
505: if ($navProps === []) {
506: return " \$this->declaredNavigationProperties = [];";
507: }
508:
509: $lines = [];
510: foreach ($navProps as $nav) {
511: $targetName = $nav->getTargetType()->getName();
512: $targetClass = $typeMap[$nav->getTargetType()->getQualifiedName()] ?? $targetName;
513: $targetFqcn = $this->namespace . '\\Types\\' . $targetClass;
514:
515: $args = ["name: '{$this->e($nav->getName())}'"];
516: $args[] = "targetType: \\{$targetFqcn}::instance()";
517: $args[] = "isCollection: {$this->bool($nav->isCollection())}";
518:
519: if (!$nav->isNullable()) {
520: $args[] = "isNullable: false";
521: }
522: if ($nav->getPartnerName() !== null) {
523: $args[] = "partnerName: '{$this->e($nav->getPartnerName())}'";
524: }
525: if ($nav->getReferentialConstraints() !== []) {
526: $constraints = $this->generateArrayLiteral($nav->getReferentialConstraints());
527: $args[] = "referentialConstraints: {$constraints}";
528: }
529:
530: $argStr = implode(', ', $args);
531: $lines[] = " new NavigationProperty({$argStr}),";
532: }
533:
534: $block = implode("\n", $lines);
535: return " \$this->declaredNavigationProperties = [\n{$block}\n ];";
536: }
537:
538: /**
539: * Generate key property references (indexes into declaredProperties).
540: *
541: * @param list<PropertyInterface> $keyProps
542: */
543: private function generateKeyReferences(array $keyProps): string
544: {
545: if ($keyProps === []) {
546: return '';
547: }
548:
549: $refs = [];
550: foreach ($keyProps as $i => $kp) {
551: $refs[] = "\$this->declaredProperties[{$i}]";
552: }
553:
554: return implode(', ', $refs);
555: }
556:
557: /**
558: * Generate navigation property binding array assignment.
559: *
560: * @param list<NavigationPropertyBindingInterface> $bindings
561: */
562: private function generateBindings(array $bindings): string
563: {
564: if ($bindings === []) {
565: return " \$this->navigationPropertyBindings = [];";
566: }
567:
568: $lines = [];
569: foreach ($bindings as $b) {
570: $lines[] = " new NavigationPropertyBinding('{$this->e($b->getPath())}', '{$this->e($b->getTarget())}'),";
571: }
572:
573: $block = implode("\n", $lines);
574: return " \$this->navigationPropertyBindings = [\n{$block}\n ];";
575: }
576:
577: /**
578: * Generate PHP code for a TypeInterface value.
579: */
580: private function generateTypeCode(TypeInterface $type, array $typeMap): string
581: {
582: if ($type instanceof \LaravelUi5\OData\Edm\Contracts\Type\PrimitiveTypeInterface) {
583: $enumCase = $type->getPrimitiveType()->name;
584: return "new PrimitiveType(EdmPrimitiveType::{$enumCase})";
585: }
586:
587: if ($type instanceof EntityTypeInterface || $type instanceof ComplexTypeInterface) {
588: $className = $typeMap[$type->getQualifiedName()] ?? $type->getName();
589: $fqcn = $this->namespace . '\\Types\\' . $className;
590: return "new \\{$fqcn}()";
591: }
592:
593: // Fallback for unknown types
594: return "new PrimitiveType(EdmPrimitiveType::String)";
595: }
596:
597: /**
598: * Generate PHP code for a FunctionInterface.
599: */
600: private function generateFunctionCode(FunctionInterface $func): string
601: {
602: $params = [];
603: foreach ($func->getParameters() as $param) {
604: $typeCode = $this->generateParamTypeCode($param->getType());
605: $params[] = "new \\LaravelUi5\\OData\\Edm\\FunctionParameter('{$this->e($param->getName())}', {$typeCode})";
606: }
607:
608: $args = ["name: '{$this->e($func->getName())}'"];
609:
610: if ($func->getReturnType() !== null) {
611: $args[] = "returnType: {$this->generateParamTypeCode($func->getReturnType())}";
612: }
613:
614: if ($params !== []) {
615: $paramStr = implode(', ', $params);
616: $args[] = "parameters: [{$paramStr}]";
617: }
618:
619: return 'new \\LaravelUi5\\OData\\Edm\\EdmFunction(' . implode(', ', $args) . ')';
620: }
621:
622: /**
623: * Generate type code for function parameters (always uses FQCN).
624: */
625: private function generateParamTypeCode(TypeInterface $type): string
626: {
627: if ($type instanceof \LaravelUi5\OData\Edm\Contracts\Type\PrimitiveTypeInterface) {
628: $enumCase = $type->getPrimitiveType()->name;
629: return "new \\LaravelUi5\\OData\\Edm\\Type\\PrimitiveType(\\LaravelUi5\\OData\\Edm\\EdmPrimitiveType::{$enumCase})";
630: }
631:
632: return "new \\LaravelUi5\\OData\\Edm\\Type\\PrimitiveType(\\LaravelUi5\\OData\\Edm\\EdmPrimitiveType::String)";
633: }
634:
635: // ── Utility ─────────────────────────────────────────────────────────────
636:
637: /**
638: * Collect all unique entity types from all schemas.
639: *
640: * @return list<EntityTypeInterface>
641: */
642: private function allEntityTypes(): array
643: {
644: $types = [];
645: foreach ($this->edmx->getSchemas() as $schema) {
646: foreach ($schema->getEntityTypes() as $type) {
647: $types[$type->getQualifiedName()] = $type;
648: }
649: }
650: return array_values($types);
651: }
652:
653: /**
654: * @return list<ComplexTypeInterface>
655: */
656: private function allComplexTypes(): array
657: {
658: $types = [];
659: foreach ($this->edmx->getSchemas() as $schema) {
660: foreach ($schema->getComplexTypes() as $type) {
661: $types[$type->getQualifiedName()] = $type;
662: }
663: }
664: return array_values($types);
665: }
666:
667: private function generateArrayLiteral(array $map): string
668: {
669: $items = [];
670: foreach ($map as $k => $v) {
671: $items[] = "'{$this->e($k)}' => '{$this->e($v)}'";
672: }
673: return '[' . implode(', ', $items) . ']';
674: }
675:
676: private function bool(bool $value): string
677: {
678: return $value ? 'true' : 'false';
679: }
680:
681: private function e(string $value): string
682: {
683: return addslashes($value);
684: }
685:
686: private function dedent(string $code): string
687: {
688: // Remove the 8-space indent from heredoc
689: return preg_replace('/^ /m', '', $code);
690: }
691:
692: private function writeFile(string $path, string $content): void
693: {
694: file_put_contents($path, $content);
695: }
696:
697: private function ensureDir(string $dir): void
698: {
699: if (!is_dir($dir)) {
700: mkdir($dir, 0755, true);
701: }
702: }
703:
704: private function emit(string $line): void
705: {
706: if ($this->output !== null) {
707: ($this->output)($line);
708: }
709: }
710: }
711: