Complex classes like TransactionBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use TransactionBuilder, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 17 | class TransactionBuilder { |
||
| 18 | |||
| 19 | const OP_RETURN = '6a'; |
||
| 20 | |||
| 21 | /** |
||
| 22 | * @var UTXO[] |
||
| 23 | */ |
||
| 24 | private $utxos = []; |
||
| 25 | |||
| 26 | /** |
||
| 27 | * @var array[] |
||
| 28 | */ |
||
| 29 | private $outputs = []; |
||
| 30 | |||
| 31 | private $changeAddress = null; |
||
| 32 | private $randomizeChangeOutput = true; |
||
| 33 | |||
| 34 | private $fee = null; |
||
| 35 | |||
| 36 | private $validateFee = null; |
||
| 37 | |||
| 38 | private $feeStrategy = Wallet::FEE_STRATEGY_OPTIMAL; |
||
| 39 | |||
| 40 | 9 | public function __construct() { |
|
| 42 | |||
| 43 | /** |
||
| 44 | * @param string $txId transactionId (hash) |
||
| 45 | * @param int $index index of the output being spent |
||
| 46 | * @param string $value when NULL we'll use the data API to fetch the value |
||
| 47 | * @param AddressInterface|string $address when NULL we'll use the data API to fetch the address |
||
| 48 | * @param ScriptInterface|string $scriptPubKey as HEX, when NULL we'll use the data API to fetch the scriptpubkey |
||
| 49 | * @param string $path when NULL we'll use the API to determine the path for the specified address |
||
| 50 | * @param ScriptInterface|string $redeemScript when NULL we'll use the path to determine the redeemscript |
||
| 51 | * @return $this |
||
| 52 | */ |
||
| 53 | 3 | public function spendOutput($txId, $index, $value = null, $address = null, $scriptPubKey = null, $path = null, $redeemScript = null, $signMode = SignInfo::MODE_SIGN) { |
|
| 66 | |||
| 67 | /** |
||
| 68 | * @return UTXO[] |
||
| 69 | */ |
||
| 70 | 3 | public function getUtxos() { |
|
| 73 | |||
| 74 | /** |
||
| 75 | * replace the currently set UTXOs with a new set |
||
| 76 | * |
||
| 77 | * @param UTXO[] $utxos |
||
| 78 | * @return $this |
||
| 79 | */ |
||
| 80 | public function setUtxos(array $utxos) { |
||
| 85 | |||
| 86 | /** |
||
| 87 | * @param string $address |
||
| 88 | * @param int $value |
||
| 89 | * @return $this |
||
| 90 | * @throws \Exception |
||
| 91 | */ |
||
| 92 | 9 | public function addRecipient($address, $value) { |
|
| 113 | |||
| 114 | /** |
||
| 115 | * add a 'raw' output, normally addRecipient or addOpReturn should be used |
||
| 116 | * |
||
| 117 | * @param array $output [value => int, address => string] |
||
| 118 | * or [value => int, scriptPubKey => string] (scriptPubKey should be hex) |
||
| 119 | * @return $this |
||
| 120 | */ |
||
| 121 | 9 | public function addOutput($output) { |
|
| 126 | |||
| 127 | /** |
||
| 128 | * @param $idx |
||
| 129 | * @param $output |
||
| 130 | * @return $this |
||
| 131 | */ |
||
| 132 | public function replaceOutput($idx, $output) { |
||
| 137 | |||
| 138 | /** |
||
| 139 | * @param $idx |
||
| 140 | * @param $value |
||
| 141 | * @return $this |
||
| 142 | * @throws \Exception |
||
| 143 | */ |
||
| 144 | public function updateOutputValue($idx, $value) { |
||
| 162 | |||
| 163 | /** |
||
| 164 | * add OP_RETURN output |
||
| 165 | * |
||
| 166 | * $data will be bin2hex and will be prefixed with a proper OP_PUSHDATA |
||
| 167 | * |
||
| 168 | * @param string $data |
||
| 169 | * @param bool $allowNonStandard when TRUE will allow scriptPubKey > 80 bytes (so $data > 80 bytes) |
||
| 170 | * @return $this |
||
| 171 | * @throws BlocktrailSDKException |
||
| 172 | */ |
||
| 173 | public function addOpReturn($data, $allowNonStandard = false) { |
||
| 174 | if (!$allowNonStandard && strlen($data) / 2 > 79) { |
||
| 175 | throw new BlocktrailSDKException("OP_RETURN data should be <= 79 bytes to remain standard!"); |
||
| 176 | } |
||
| 177 | |||
| 178 | $script = ScriptFactory::create() |
||
| 179 | ->op('OP_RETURN') |
||
| 180 | ->push(new Buffer($data)) |
||
| 181 | ->getScript() |
||
| 182 | ; |
||
| 183 | |||
| 184 | $this->addOutput([ |
||
| 185 | 'scriptPubKey' => $script, |
||
| 186 | 'value' => 0 |
||
| 187 | ]); |
||
| 188 | |||
| 189 | return $this; |
||
| 190 | } |
||
| 191 | |||
| 192 | /** |
||
| 193 | * @param bool $json return data for JSON return (so objects -> string) |
||
| 194 | * @return array |
||
| 195 | */ |
||
| 196 | public function getOutputs($json = false) { |
||
| 197 | 9 | return array_map(function ($output) use ($json) { |
|
| 198 | 9 | $result = $output; |
|
| 199 | |||
| 200 | 9 | if ($json) { |
|
| 201 | 8 | if (isset($result['scriptPubKey']) && $result['scriptPubKey'] instanceof ScriptInterface) { |
|
| 202 | $result['scriptPubKey'] = $result['scriptPubKey']->getHex(); |
||
| 203 | } |
||
| 204 | 8 | if (isset($result['address']) && $result['address'] instanceof AddressInterface) { |
|
| 205 | $result['address'] = $result['address']->getAddress(); |
||
| 206 | } |
||
| 207 | } |
||
| 208 | |||
| 209 | 9 | return $result; |
|
| 210 | 9 | }, $this->outputs); |
|
| 211 | } |
||
| 212 | |||
| 213 | /** |
||
| 214 | * set change address |
||
| 215 | * |
||
| 216 | * @param string $address |
||
| 217 | * @return $this |
||
| 218 | */ |
||
| 219 | 9 | public function setChangeAddress($address) { |
|
| 224 | |||
| 225 | /** |
||
| 226 | * @return string|null |
||
| 227 | */ |
||
| 228 | 3 | public function getChangeAddress() { |
|
| 231 | |||
| 232 | /** |
||
| 233 | * @param string $feeStrategy |
||
| 234 | * @return $this |
||
| 235 | * @throws BlocktrailSDKException |
||
| 236 | */ |
||
| 237 | 9 | public function setFeeStrategy($feeStrategy) { |
|
| 246 | |||
| 247 | /** |
||
| 248 | * @return string |
||
| 249 | */ |
||
| 250 | 9 | public function getFeeStrategy() { |
|
| 253 | |||
| 254 | /** |
||
| 255 | * @param bool $randomizeChangeOutput |
||
| 256 | * @return $this |
||
| 257 | */ |
||
| 258 | 9 | public function randomizeChangeOutput($randomizeChangeOutput = true) { |
|
| 263 | |||
| 264 | /** |
||
| 265 | * @return bool |
||
| 266 | */ |
||
| 267 | 3 | public function shouldRandomizeChangeOuput() { |
|
| 270 | |||
| 271 | /** |
||
| 272 | * set desired fee (normally automatically calculated) |
||
| 273 | * |
||
| 274 | * @param int $value |
||
| 275 | * @return $this |
||
| 276 | */ |
||
| 277 | 1 | public function setFee($value) { |
|
| 287 | |||
| 288 | /** |
||
| 289 | * @return int|null |
||
| 290 | */ |
||
| 291 | 3 | public function getFee() { |
|
| 294 | |||
| 295 | /** |
||
| 296 | * @param int $fee |
||
| 297 | * @return $this |
||
| 298 | */ |
||
| 299 | 2 | public function validateFee($fee) { |
|
| 304 | |||
| 305 | /** |
||
| 306 | * @return int|null |
||
| 307 | */ |
||
| 308 | 3 | public function getValidateFee() { |
|
| 311 | } |
||
| 312 |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.