1 <?php
2
3 namespace Docolight\Support;
4
5 use Closure;
6 use InvalidArgumentException;
7 use Docolight\Container\Container;
8
9 /**
10 * In object-oriented programming, a factory is an object for creating other objects – formally a factory is simply an object that returns an object from some method call, which is assumed to be "new". More broadly, a subroutine that returns a "new" object may be referred to as a "factory", as in factory method or factory function. This is a basic concept in OOP, and forms the basis for a number of related software design patterns.
11 *
12 * @author Krisan Alfa Timur <krisanalfa@docotel.co.id>
13 *
14 * @link https://en.wikipedia.org/wiki/Factory_(object-oriented_programming) Factory (object-oriented programming)
15 */
16 abstract class Factory
17 {
18 /**
19 * The container instance.
20 *
21 * @var
22 */
23 protected $container;
24
25 /**
26 * The registered custom product creators.
27 *
28 * @var array
29 */
30 protected $customCreators = [];
31
32 /**
33 * The array of created "products".
34 *
35 * @var array
36 */
37 protected $products = [];
38
39 /**
40 * Create a new manager instance.
41 *
42 * @param Container $container
43 * @return void
44 */
45 public function __construct(Container $container)
46 {
47 $this->container = $container;
48 }
49
50 /**
51 * Get the default product name.
52 *
53 * @return string
54 */
55 abstract public function getDefaultProduct();
56
57 /**
58 * Get a product instance.
59 *
60 * @param string $product
61 * @return mixed
62 */
63 public function produce($product = null)
64 {
65 $product = $product ?: $this->getDefaultProduct();
66 $identifier = camel_case($product);
67
68 // If the given product has not been created before, we will create the instances
69 // here and cache it so we can return it next time very quickly. If there is
70 // already a product created by this name, we'll just return that instance.
71 if (!isset($this->products[$identifier])) {
72 $this->products[$identifier] = $this->createProduct($product);
73 }
74
75 return $this->products[$identifier];
76 }
77
78 /**
79 * Create a new product instance.
80 *
81 * @param string $product
82 * @return mixed
83 *
84 * @throws \InvalidArgumentException
85 */
86 protected function createProduct($product)
87 {
88 $method = $this->getMethod($product);
89
90 // We'll check to see if a creator method exists for the given product. If not we
91 // will check for a custom product creator, which allows developers to create
92 // products using their own customized product creator Closure to create it.
93 if (isset($this->customCreators[$product])) {
94 return $this->callCustomCreator($product);
95 } elseif (method_exists($this, $method)) {
96 return $this->$method();
97 }
98
99 throw new InvalidArgumentException("Product [$product] not supported.");
100 }
101
102 /**
103 * Get method to create product.
104 *
105 * @param string $product
106 *
107 * @return string
108 */
109 protected function getMethod($product)
110 {
111 return 'create'.ucfirst($product).'Product';
112 }
113
114 /**
115 * Call a custom product creator.
116 *
117 * @param string $product
118 * @return mixed
119 */
120 protected function callCustomCreator($product)
121 {
122 $productClosure = $this->customCreators[$product];
123
124 return $productClosure($this->container);
125 }
126
127 /**
128 * Register a custom product creator Closure.
129 *
130 * @param string $product
131 * @param \Closure $callback
132 * @return $this
133 */
134 public function extend($product, Closure $callback)
135 {
136 $this->customCreators[$product] = $callback;
137
138 return $this;
139 }
140
141 /**
142 * Dynamically call the default product instance.
143 *
144 * @param string $method
145 * @param array $parameters
146 * @return mixed
147 */
148 public function __call($method, $parameters)
149 {
150 return call_user_func_array(array($this->produce(), $method), $parameters);
151 }
152 }
153