1: <?php
2:
3: namespace Charcoal\Model\Service;
4:
5: use \Exception;
6:
7: // Module `charcoal-factory` dependencies
8: use \Charcoal\Factory\FactoryInterface;
9:
10: // Local module (`charcoal-core`) dependencies
11: use \Charcoal\Model\ModelMetadata;
12: use \Charcoal\Model\Service\MetadataLoader;
13:
14: /**
15: *
16: */
17: final class ModelBuilder
18: {
19: const DEFAULT_SOURCE_TYPE = 'database';
20:
21: /**
22: * @var FactoryInterface
23: */
24: private $factory;
25:
26: /**
27: * @var MetadataLoader
28: */
29: private $metadataLoader;
30:
31: /**
32: * @var FactoryInterface
33: */
34: private $sourceFactory;
35:
36: /**
37: * @param array $data Constructor dependencies.
38: */
39: public function __construct(array $data)
40: {
41: $this->setFactory($data['factory']);
42: $this->setMetadataLoader($data['metadata_loader']);
43: $this->setSourceFactory($data['source_factory']);
44: }
45:
46: /**
47: * Build a model, pre-initializing its metadata and its source.
48: *
49: * By default, the name of the "obj type" (the model class name) is used to determine the metadata ident to load.
50: * To load a custom metadata for the object, use the `$metadataIdent` argument.
51: *
52: * By default, the object's _default_ source (from its metadata) is used as source.
53: * To load a custom source for the object, use the `$sourceIdent` argument.
54: *
55: * @param string $objType The object type of the Model.
56: * @param string|null $metadataIdent Optional. The metadata ident of the object.
57: * @param string|null $sourceIdent Optional. The custom source ident to load as source.
58: * @return ModelInterface
59: */
60: public function build($objType, $metadataIdent = null, $sourceIdent = null)
61: {
62: $metadata = $this->createMetadata($objType, $metadataIdent);
63: $source = $this->createSource($metadata, $sourceIdent);
64: $args = array_merge($this->factory->arguments(), [
65: 'metadata' => $metadata,
66: 'source' => $source
67: ]);
68: $model = $this->factory->create($objType, $args);
69: $model->source()->setModel($model);
70: return $model;
71: }
72:
73: /**
74: * The builder can be invoked (used as function).
75: *
76: * @param string $objType The object type of the Model.
77: * @param string|null $metadataIdent Optional. The metadata ident of the object.
78: * @param string|null $sourceIdent Optional. The custom source ident to load as source.
79: * @return ModelInterface
80: */
81: public function __invoke($objType, $metadataIdent = null, $sourceIdent = null)
82: {
83: return $this->build($objType, $metadataIdent, $sourceIdent);
84: }
85:
86:
87: /**
88: * @param FactoryInterface $factory The factory to use to create models.
89: * @return ModelLoaderBuilder Chainable
90: */
91: private function setFactory(FactoryInterface $factory)
92: {
93: $this->factory = $factory;
94: return $this;
95: }
96:
97: /**
98: * @param MetadataLoader $loader The loader instance, used to load metadata.
99: * @return DescribableInterface Chainable
100: */
101: private function setMetadataLoader(MetadataLoader $loader)
102: {
103: $this->metadataLoader = $loader;
104: return $this;
105: }
106:
107: /**
108: * @param FactoryInterface $factory The factory to use to create models.
109: * @return ModelLoaderBuilder Chainable
110: */
111: private function setSourceFactory(FactoryInterface $factory)
112: {
113: $this->sourceFactory = $factory;
114: return $this;
115: }
116:
117: /**
118: * @param string $objType The type of object to load.
119: * (A class name or object identifier).
120: * @param string|null $metadataIdent Optional. The metadata identifier to load.
121: * If NULL, it will be implied from objType.
122: * @return MetadataInterface
123: */
124: private function createMetadata($objType, $metadataIdent = null)
125: {
126: $metadataIdent = ($metadataIdent !== null) ? $metadataIdent : $objType;
127: return $this->metadataLoader->load($metadataIdent, new ModelMetadata());
128: }
129:
130: /**
131: * @param ModelMetadata $metadata The object metadata, where to find the object's
132: * source configuration.
133: * @param string|null $sourceIdent Optional. Custom source ident to load.
134: * If NULL, the default (from metadata) will be used.
135: * @throws Exception If the source is not defined in the model's metadata.
136: * @return SourceInterface
137: */
138: private function createSource(ModelMetadata $metadata, $sourceIdent = null)
139: {
140: if ($sourceIdent === null) {
141: $sourceIdent = $metadata->defaultSource();
142: }
143: $sourceConfig = $metadata->source($sourceIdent);
144:
145: if (!$sourceConfig) {
146: throw new Exception(
147: sprintf('Can not create %s source: "%s" is not defined in metadata.', get_class($this), $sourceIdent)
148: );
149: }
150:
151: $sourceType = isset($sourceConfig['type']) ? $sourceConfig['type'] : self::DEFAULT_SOURCE_TYPE;
152: $source = $this->sourceFactory->create($sourceType);
153: $source->setData($sourceConfig);
154:
155: return $source;
156: }
157: }
158: