Source Code: SmartFactory/DatabaseWorkers/ShardManager.php

  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