Charlie PHP Expert — Volume 03.1 Corrective Training: Signed Numbers, Boundary Conditions, Domain Invariants & Overdraft Policies Purpose: Correct a recurring reasoning error involving negative-number comparisons and connect that reasoning to safe PHP domain design. This supplement does not replace Volume 03. 1. Signed-number comparison On a number line, values farther to the right are greater. A negative number closer to zero is greater than a more negative number. -200 > -500 -500 < -200 -500 is the lower value. Do not reason from the size of the digits alone. The minus sign changes ordering. For example, -750 is less than -500, while -1 is greater than -500. 2. Minimum-balance boundary rule If an account has a minimum allowed balance of -500, every balance greater than or equal to -500 is valid. Only a value below -500 is invalid. Balance Classification Reason 100 VALID 100 ³ -500 0 VALID 0 ³ -500 -1 VALID -1 ³ -500 -200 VALID -200 ³ -500 -500 VALID Exactly at the allowed minimum -501 INVALID -501 < -500 -750 INVALID -750 < -500 The PHP rejection condition is: if ($balance < $minimumBalance) { throw new DomainException('Balance below allowed minimum.'); } Boundary principle: If -500 itself is allowed, do not use <=. The invalid region begins strictly below -500. 3. Domain invariant A domain invariant is a rule that must always remain true for a valid object. The BankAccount object should protect its own balance invariant instead of relying on controllers, APIs, or services to remember the rule. Checking the rule only outside the object is weaker because one caller can forget the check, use a different threshold, or mutate the account through another path. 4. Validate before mutating Calculate the proposed state first, validate it, and only then commit it. Do not change the object's balance and attempt to repair it after validation fails. $newBalance = $this->balance - $amount; $this->policy->assertValid($newBalance); $this->balance = $newBalance; This ordering keeps the object valid even when validation throws an exception. 5. Policy as the single authority The account should not contain a separate rule such as 'negative means invalid' if overdraft accounts are supported. The policy decides whether the proposed resulting balance is permitted. interface BalancePolicy { public function assertValid(float $balance): void; } final class MinimumBalancePolicy implements BalancePolicy { public function __construct(private float $minimumBalance) {} public function assertValid(float $balance): void { if ($balance < $this->minimumBalance) { throw new DomainException('Balance below allowed minimum.'); } } } Standard account: MinimumBalancePolicy(0.0). Overdraft account: MinimumBalancePolicy(-500.0). The BankAccount does not need account-type conditionals. 6. Common failure patterns Failure A — Wrong signed-number comparison: saying -200 is less than -500. Correction: -200 is greater than -500. Failure B — Generic negativity check: rejecting every balance below zero. This destroys legitimate overdraft behavior. Failure C — Duplicate authority: Account::withdraw() blocks overdraft before the policy evaluates the resulting balance. Failure D — Mutate then validate: the object can temporarily enter invalid state. Validate the proposed state first. Failure E — Scattered account-type conditionals: controllers and services branch on account type. Prefer a policy abstraction so the invariant has one authority. 7. Self-check procedure Before answering a balance-rule problem, Charlie should perform these checks internally: 1. Identify the exact minimum or maximum boundary. 2. Test one value above, exactly at, and below the boundary. 3. Verify signed-number ordering. 4. Determine whether the boundary itself is inclusive. 5. Write the boolean condition only after the examples agree. 6. Confirm the domain object cannot bypass the policy. 7. Confirm validation occurs before mutation. 8. Re-read every requested subpart before finalizing the answer. 8. Training exercises Exercise 1: Minimum = -300. Classify: 50, 0, -1, -299, -300, -301, -900. Exercise 2: Explain the difference between $balance < $minimum and $balance <= $minimum when the minimum itself is allowed. Exercise 3: An account has balance 100 and minimum -500. Determine whether withdrawals of 300, 600, and 601 are permitted. Calculate the proposed balance before deciding. Exercise 4: Find the design bug in a withdraw method that checks $this->balance >= $amount before invoking an overdraft policy. Exercise 5: Explain why making the balance public weakens encapsulation rather than strengthening it. Certification readiness Volume 03.1 is considered absorbed when Charlie can consistently compare signed values, handle inclusive boundaries, preserve invariants, validate before mutation, and apply a balance policy without contradictory checks. Certification questions should be new scenarios, not copies of these exercises. Charlie PHP Expert — Volume 03.1 Corrective Training: Signed Numbers, Boundary Conditions, Domain Invariants & Overdraft Policies Purpose: Correct a recurring reasoning error involving negative-number comparisons and connect that reasoning to safe PHP domain design. This supplement does not replace Volume 03. 1. Signed-number comparison On a number line, values farther to the right are greater. A negative number closer to zero is greater than a more negative number. -200 > -500 -500 < -200 -500 is the lower value. Do not reason from the size of the digits alone. The minus sign changes ordering. For example, -750 is less than -500, while -1 is greater than -500. 2. Minimum-balance boundary rule If an account has a minimum allowed balance of -500, every balance greater than or equal to -500 is valid. Only a value below -500 is invalid. Balance Classification Reason 100 VALID 100 ³ -500 0 VALID 0 ³ -500 -1 VALID -1 ³ -500 -200 VALID -200 ³ -500 -500 VALID Exactly at the allowed minimum -501 INVALID -501 < -500 -750 INVALID -750 < -500 The PHP rejection condition is: if ($balance < $minimumBalance) { throw new DomainException('Balance below allowed minimum.'); } Boundary principle: If -500 itself is allowed, do not use <=. The invalid region begins strictly below -500. 3. Domain invariant A domain invariant is a rule that must always remain true for a valid object. The BankAccount object should protect its own balance invariant instead of relying on controllers, APIs, or services to remember the rule. Checking the rule only outside the object is weaker because one caller can forget the check, use a different threshold, or mutate the account through another path. 4. Validate before mutating Calculate the proposed state first, validate it, and only then commit it. Do not change the object's balance and attempt to repair it after validation fails. $newBalance = $this->balance - $amount; $this->policy->assertValid($newBalance); $this->balance = $newBalance; This ordering keeps the object valid even when validation throws an exception. 5. Policy as the single authority The account should not contain a separate rule such as 'negative means invalid' if overdraft accounts are supported. The policy decides whether the proposed resulting balance is permitted. interface BalancePolicy { public function assertValid(float $balance): void; } final class MinimumBalancePolicy implements BalancePolicy { public function __construct(private float $minimumBalance) {} public function assertValid(float $balance): void { if ($balance < $this->minimumBalance) { throw new DomainException('Balance below allowed minimum.'); } } } Standard account: MinimumBalancePolicy(0.0). Overdraft account: MinimumBalancePolicy(-500.0). The BankAccount does not need account-type conditionals. 6. Common failure patterns Failure A — Wrong signed-number comparison: saying -200 is less than -500. Correction: -200 is greater than -500. Failure B — Generic negativity check: rejecting every balance below zero. This destroys legitimate overdraft behavior. Failure C — Duplicate authority: Account::withdraw() blocks overdraft before the policy evaluates the resulting balance. Failure D — Mutate then validate: the object can temporarily enter invalid state. Validate the proposed state first. Failure E — Scattered account-type conditionals: controllers and services branch on account type. Prefer a policy abstraction so the invariant has one authority. 7. Self-check procedure Before answering a balance-rule problem, Charlie should perform these checks internally: 1. Identify the exact minimum or maximum boundary. 2. Test one value above, exactly at, and below the boundary. 3. Verify signed-number ordering. 4. Determine whether the boundary itself is inclusive. 5. Write the boolean condition only after the examples agree. 6. Confirm the domain object cannot bypass the policy. 7. Confirm validation occurs before mutation. 8. Re-read every requested subpart before finalizing the answer. 8. Training exercises Exercise 1: Minimum = -300. Classify: 50, 0, -1, -299, -300, -301, -900. Exercise 2: Explain the difference between $balance < $minimum and $balance <= $minimum when the minimum itself is allowed. Exercise 3: An account has balance 100 and minimum -500. Determine whether withdrawals of 300, 600, and 601 are permitted. Calculate the proposed balance before deciding. Exercise 4: Find the design bug in a withdraw method that checks $this->balance >= $amount before invoking an overdraft policy. Exercise 5: Explain why making the balance public weakens encapsulation rather than strengthening it. Certification readiness Volume 03.1 is considered absorbed when Charlie can consistently compare signed values, handle inclusive boundaries, preserve invariants, validate before mutation, and apply a balance policy without contradictory checks. Certification questions should be new scenarios, not copies of these exercises. Charlie PHP Expert — Volume 03.1 Corrective Training: Signed Numbers, Boundary Conditions, Domain Invariants & Overdraft Policies Purpose: Correct a recurring reasoning error involving negative-number comparisons and connect that reasoning to safe PHP domain design. This supplement does not replace Volume 03. 1. Signed-number comparison On a number line, values farther to the right are greater. A negative number closer to zero is greater than a more negative number. -200 > -500 -500 < -200 -500 is the lower value. Do not reason from the size of the digits alone. The minus sign changes ordering. For example, -750 is less than -500, while -1 is greater than -500. 2. Minimum-balance boundary rule If an account has a minimum allowed balance of -500, every balance greater than or equal to -500 is valid. Only a value below -500 is invalid. Balance Classification Reason 100 VALID 100 ³ -500 0 VALID 0 ³ -500 -1 VALID -1 ³ -500 -200 VALID -200 ³ -500 -500 VALID Exactly at the allowed minimum -501 INVALID -501 < -500 -750 INVALID -750 < -500 The PHP rejection condition is: if ($balance < $minimumBalance) { throw new DomainException('Balance below allowed minimum.'); } Boundary principle: If -500 itself is allowed, do not use <=. The invalid region begins strictly below -500. 3. Domain invariant A domain invariant is a rule that must always remain true for a valid object. The BankAccount object should protect its own balance invariant instead of relying on controllers, APIs, or services to remember the rule. Checking the rule only outside the object is weaker because one caller can forget the check, use a different threshold, or mutate the account through another path. 4. Validate before mutating Calculate the proposed state first, validate it, and only then commit it. Do not change the object's balance and attempt to repair it after validation fails. $newBalance = $this->balance - $amount; $this->policy->assertValid($newBalance); $this->balance = $newBalance; This ordering keeps the object valid even when validation throws an exception. 5. Policy as the single authority The account should not contain a separate rule such as 'negative means invalid' if overdraft accounts are supported. The policy decides whether the proposed resulting balance is permitted. interface BalancePolicy { public function assertValid(float $balance): void; } final class MinimumBalancePolicy implements BalancePolicy { public function __construct(private float $minimumBalance) {} public function assertValid(float $balance): void { if ($balance < $this->minimumBalance) { throw new DomainException('Balance below allowed minimum.'); } } } Standard account: MinimumBalancePolicy(0.0). Overdraft account: MinimumBalancePolicy(-500.0). The BankAccount does not need account-type conditionals. 6. Common failure patterns Failure A — Wrong signed-number comparison: saying -200 is less than -500. Correction: -200 is greater than -500. Failure B — Generic negativity check: rejecting every balance below zero. This destroys legitimate overdraft behavior. Failure C — Duplicate authority: Account::withdraw() blocks overdraft before the policy evaluates the resulting balance. Failure D — Mutate then validate: the object can temporarily enter invalid state. Validate the proposed state first. Failure E — Scattered account-type conditionals: controllers and services branch on account type. Prefer a policy abstraction so the invariant has one authority. 7. Self-check procedure Before answering a balance-rule problem, Charlie should perform these checks internally: 1. Identify the exact minimum or maximum boundary. 2. Test one value above, exactly at, and below the boundary. 3. Verify signed-number ordering. 4. Determine whether the boundary itself is inclusive. 5. Write the boolean condition only after the examples agree. 6. Confirm the domain object cannot bypass the policy. 7. Confirm validation occurs before mutation. 8. Re-read every requested subpart before finalizing the answer. 8. Training exercises Exercise 1: Minimum = -300. Classify: 50, 0, -1, -299, -300, -301, -900. Exercise 2: Explain the difference between $balance < $minimum and $balance <= $minimum when the minimum itself is allowed. Exercise 3: An account has balance 100 and minimum -500. Determine whether withdrawals of 300, 600, and 601 are permitted. Calculate the proposed balance before deciding. Exercise 4: Find the design bug in a withdraw method that checks $this->balance >= $amount before invoking an overdraft policy. Exercise 5: Explain why making the balance public weakens encapsulation rather than strengthening it. Certification readiness Volume 03.1 is considered absorbed when Charlie can consistently compare signed values, handle inclusive boundaries, preserve invariants, validate before mutation, and apply a balance policy without contradictory checks. Certification questions should be new scenarios, not copies of these exercises. Charlie PHP Expert — Volume 03.1 Corrective Training: Signed Numbers, Boundary Conditions, Domain Invariants & Overdraft Policies Purpose: Correct a recurring reasoning error involving negative-number comparisons and connect that reasoning to safe PHP domain design. This supplement does not replace Volume 03. 1. Signed-number comparison On a number line, values farther to the right are greater. A negative number closer to zero is greater than a more negative number. -200 > -500 -500 < -200 -500 is the lower value. Do not reason from the size of the digits alone. The minus sign changes ordering. For example, -750 is less than -500, while -1 is greater than -500. 2. Minimum-balance boundary rule If an account has a minimum allowed balance of -500, every balance greater than or equal to -500 is valid. Only a value below -500 is invalid. Balance Classification Reason 100 VALID 100 ³ -500 0 VALID 0 ³ -500 -1 VALID -1 ³ -500 -200 VALID -200 ³ -500 -500 VALID Exactly at the allowed minimum -501 INVALID -501 < -500 -750 INVALID -750 < -500 The PHP rejection condition is: if ($balance < $minimumBalance) { throw new DomainException('Balance below allowed minimum.'); } Boundary principle: If -500 itself is allowed, do not use <=. The invalid region begins strictly below -500. 3. Domain invariant A domain invariant is a rule that must always remain true for a valid object. The BankAccount object should protect its own balance invariant instead of relying on controllers, APIs, or services to remember the rule. Checking the rule only outside the object is weaker because one caller can forget the check, use a different threshold, or mutate the account through another path. 4. Validate before mutating Calculate the proposed state first, validate it, and only then commit it. Do not change the object's balance and attempt to repair it after validation fails. $newBalance = $this->balance - $amount; $this->policy->assertValid($newBalance); $this->balance = $newBalance; This ordering keeps the object valid even when validation throws an exception. 5. Policy as the single authority The account should not contain a separate rule such as 'negative means invalid' if overdraft accounts are supported. The policy decides whether the proposed resulting balance is permitted. interface BalancePolicy { public function assertValid(float $balance): void; } final class MinimumBalancePolicy implements BalancePolicy { public function __construct(private float $minimumBalance) {} public function assertValid(float $balance): void { if ($balance < $this->minimumBalance) { throw new DomainException('Balance below allowed minimum.'); } } } Standard account: MinimumBalancePolicy(0.0). Overdraft account: MinimumBalancePolicy(-500.0). The BankAccount does not need account-type conditionals. 6. Common failure patterns Failure A — Wrong signed-number comparison: saying -200 is less than -500. Correction: -200 is greater than -500. Failure B — Generic negativity check: rejecting every balance below zero. This destroys legitimate overdraft behavior. Failure C — Duplicate authority: Account::withdraw() blocks overdraft before the policy evaluates the resulting balance. Failure D — Mutate then validate: the object can temporarily enter invalid state. Validate the proposed state first. Failure E — Scattered account-type conditionals: controllers and services branch on account type. Prefer a policy abstraction so the invariant has one authority. 7. Self-check procedure Before answering a balance-rule problem, Charlie should perform these checks internally: 1. Identify the exact minimum or maximum boundary. 2. Test one value above, exactly at, and below the boundary. 3. Verify signed-number ordering. 4. Determine whether the boundary itself is inclusive. 5. Write the boolean condition only after the examples agree. 6. Confirm the domain object cannot bypass the policy. 7. Confirm validation occurs before mutation. 8. Re-read every requested subpart before finalizing the answer. 8. Training exercises Exercise 1: Minimum = -300. Classify: 50, 0, -1, -299, -300, -301, -900. Exercise 2: Explain the difference between $balance < $minimum and $balance <= $minimum when the minimum itself is allowed. Exercise 3: An account has balance 100 and minimum -500. Determine whether withdrawals of 300, 600, and 601 are permitted. Calculate the proposed balance before deciding. Exercise 4: Find the design bug in a withdraw method that checks $this->balance >= $amount before invoking an overdraft policy. Exercise 5: Explain why making the balance public weakens encapsulation rather than strengthening it. Certification readiness Volume 03.1 is considered absorbed when Charlie can consistently compare signed values, handle inclusive boundaries, preserve invariants, validate before mutation, and apply a balance policy without contradictory checks. Certification questions should be new scenarios, not copies of these exercises.