1: <?php
2: /**
3: * This file contains the implementation of the class ShardManager
4: * for managing connections to the DB shards.
5: *
6: * @package Database
7: *
8: * @author Oleg Schildt
9: */
10:
11: namespace SmartFactory\DatabaseWorkers;
12:
13: use \SmartFactory\Interfaces\IShardManager;
14:
15: use function \SmartFactory\dbworker;
16:
17: /**
18: * Class for managing connections to the DB shards.
19: *
20: * This shard manager allows registering multiple shards and request connections to them.
21: * It ensures that only one connection to a shard is used within a request.
22: *
23: * @see DBWorker
24: *
25: * @author Oleg Schildt
26: */
27: class ShardManager implements IShardManager
28: {
29: /**
30: * Internal array for storing the load balancing groups and its shards.
31: *
32: * @var array
33: *
34: * @author Oleg Schildt
35: */
36: protected array $load_balancing_groups = [];
37:
38: /**
39: * Internal array for storing the mapping between shard names and connection parameters.
40: *
41: * @var array
42: *
43: * @author Oleg Schildt
44: */
45: protected array $shard_table = [];
46:
47: /**
48: * Registers a new shard.
49: *
50: * @param string $shard_name
51: * Unique shard name.
52: *
53: * @param array $parameters
54: * The connection parameters to the shard as an associative array in the form key => value:
55: *
56: * - $parameters["db_type"] - type of the database (MySQL or MSSQL)
57: * - $parameters["db_server"] - server address
58: * - $parameters["db_name"] - database name
59: * - $parameters["db_user"] - user name
60: * - $parameters["db_password"] - user password
61: * - $parameters["autoconnect"] - should true if the connection should be established automatically upon creation.
62: * - $parameters["read_only"] - this parameter sets the connection to the read only mode.
63: *
64: * @param string $load_balancing_group
65: * The name of the load balancing group, if the shard should be part of it, {@see ShardManager::randomDBShard()}.
66: *
67: * @return bool
68: * It should return true if the registering was successful, otherwise false.
69: *
70: * @throws \Exception
71: * It might throw an exception in the case of any errors:
72: *
73: * - if the shard name is not specified.
74: * - if the shard name has been already registered.
75: *
76: * @author Oleg Schildt
77: */
78: public function registerShard(string $shard_name, array $parameters, string $load_balancing_group = ""): bool
79: {
80: if (empty($shard_name)) {
81: throw new \Exception("The shard name is not specified!");
82: }
83:
84: if (!empty($this->shard_table[$shard_name])) {
85: throw new \Exception("The shard '$shard_name' has been already registered!");
86: }
87:
88: $this->shard_table[$shard_name]["parameters"] = $parameters;
89:
90: if (!empty($load_balancing_group)) {
91: $this->load_balancing_groups[$load_balancing_group][] = $shard_name;
92: }
93:
94: return true;
95: } // registerShard
96:
97: /**
98: * The method provides the DBWorker object for working with the shard.
99: *
100: * If the parameters are omitted, the system takes the parameters from the configuration
101: * settings and reuses the single instance of the DBWorker for all requests.
102: * If the user passes the parameters explicitly, a new instance of the DBWorker is created upon each new request.
103: *
104: * Currently supported: MySQL, PostgreSQL und MS SQL.
105: *
106: * @param string $shard_name
107: * The name of the shard.
108: *
109: * @return ?\SmartFactory\DatabaseWorkers\DBWorker
110: * returns DBWorker object or null if the object could not be created.
111: *
112: * @throws \Exception
113: * It might throw an exception in the case of any errors:
114: *
115: * - if the interface or class does not exist.
116: * - if the shard was not found.
117: * - if the check of the classes and interfaces fails.
118: * - if the php extension is not installed.
119: * - db_missing_type_error - if the database type is not specified.
120: * - db_conn_data_error - if the connection parameters are incomplete.
121: * - db_server_conn_error - if the database server cannot be connected.
122: * - db_not_exists_error - if database does not exist od inaccessible to the user.
123: *
124: * @author Oleg Schildt
125: */
126: public function dbshard(string $shard_name): ?\SmartFactory\DatabaseWorkers\DBWorker
127: {
128: if (empty($shard_name) || empty($this->shard_table[$shard_name])) {
129: throw new \Exception("The shard '$shard_name' was not found!");
130: }
131:
132: // Important!
133: //
134: // Many different databases may be connected within one request,
135: // therefore, we request the dbworker as not singleton. Otherwise,
136: // only one dbworker connected to one database would be returned.
137: //
138: // But, as soon as, we got a shard connected to a required database,
139: // we keep it as singleton within one request, to avoid the unnecessary
140: // DB connections.
141: //
142: // In other words, many databases can be accessed from one request,
143: // but there is only one connection to each database.
144:
145: if (empty($this->shard_table[$shard_name]["dbworker"])) {
146: $this->shard_table[$shard_name]["dbworker"] = dbworker($this->shard_table[$shard_name]["parameters"], false /* not singleton */);
147: }
148:
149: return $this->shard_table[$shard_name]["dbworker"];
150: } // dbshard
151:
152: /**
153: * The method provides the DBWorker object for working with the shard, that is chosen randomly
154: * for load balancing reason.
155: *
156: * @param string $load_balancing_group
157: * The name of the load balancing group, from which the shard should be randomly picked.
158: *
159: * @return ?\SmartFactory\DatabaseWorkers\DBWorker
160: * returns DBWorker object or null if the object could not be created.
161: *
162: * @throws \Exception
163: * It might throw an exception in the case of any errors:
164: *
165: * - if the load balancing group was not found.
166: * - db_server_conn_error - if the database server cannot be connected.
167: * - db_not_exists_error - if database does not exist od inaccesible to the user.
168: *
169: * @author Oleg Schildt
170: */
171: public function randomDBShard(string $load_balancing_group): ?\SmartFactory\DatabaseWorkers\DBWorker
172: {
173: if (empty($this->load_balancing_groups[$load_balancing_group])) {
174: throw new \Exception("The group '$load_balancing_group' was not found!");
175: }
176:
177: $pos = rand(0, count($this->load_balancing_groups[$load_balancing_group]) - 1);
178:
179: return $this->dbshard($this->load_balancing_groups[$load_balancing_group][$pos]);
180: } // randomDBShard
181: } // ShardManager