Why We Need Secondary Constructors and How to Implement Them in Kotlin
The article explains the importance of auxiliary (secondary) constructors for flexible object creation, contrasts PHP's lack of support with Kotlin's feature, and demonstrates clean implementations using primary constructors and static factory methods to improve readability and maintainability.
Auxiliary (or "multiple") constructors play a crucial role during object creation by adding an extra logical layer, allowing flexible adjustment of initial parameters and enhancing maintainability.
Unfortunately, the PHP language does not support auxiliary constructors, whereas Kotlin provides this capability.
To illustrate, consider a KYC questionnaire where required fields vary according to the user's employment status (employed, self‑employed, retired, unemployed, etc.).
final class Questionnaire { public function __construct( private QuestionnaireId $questionnaireId, private EmploymentStatus $employmentStatus, private ?string $incomeSource, private ?array $annualIncomeRange, ) { } // ... other methods ... } $employed = new Questionnaire(EmploymentStatus::EMPLOYED, 'Company Name', [60000, 80000]); $selfEmployed = new Questionnaire(EmploymentStatus::SELF_EMPLOYED, null, [50000, 60000]); $retired = new Questionnaire(EmploymentStatus::RETIRED, null, [20000, 30000]); $unemployed = new Questionnaire(EmploymentStatus::UNEMPLOYED, null, null);
This approach, however, permits inconsistent objects such as unemployed persons with income, retired persons with company information, or employed persons without income, unless additional validation logic is added.
The cleaner solution is to use a private constructor together with public static factory methods, effectively creating secondary constructors:
final class Questionnaire { private function __construct( private EmploymentStatus $employmentStatus, private ?string $incomeSource, private ?array $annualIncomeRange, ) { } public static function asEmployed(string $incomeSource, array $annualIncomeRange): self { return new self(EmploymentStatus::EMPLOYED, $incomeSource, $annualIncomeRange); } public static function asSelfEmployed(array $annualIncomeRange): self { return new self(EmploymentStatus::SELF_EMPLOYED, null, $annualIncomeRange); } public static function asRetired(array $annualIncomeRange): self { return new self(EmploymentStatus::RETIRED, null, $annualIncomeRange); } public static function asUnemployed(): self { return new self(EmploymentStatus::UNEMPLOYED, null, null); } // ... other methods ... } $employed = Questionnaire::asEmployed('Company name', [50000, 80000]); $selfEmployed = Questionnaire::asSelfEmployed([40000, 60000]); $retired = Questionnaire::asRetired([30000, 50000]); $unemployed = Questionnaire::asUnemployed();
Developers can thus avoid scattered conditional logic, achieve a consistent data model, and greatly improve code readability and maintainability by relying on clearly named factory methods that encapsulate business rules.
php中文网 Courses
php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.