*/ public const VALID_REGISTRATION_TYPES = [ self::REG_ACCOUNT, self::REG_LESSON, self::REG_ENROLLMENT ]; public function __construct( public readonly int $policyVersionId, public readonly int $studentId, public readonly string $registrationType, public readonly int $registrationId, /** * Who actually clicked "I agree" — a child's guardian, or 0 meaning the * student agreed for themselves. Zero rather than a copy of `studentId` * so every acceptance recorded before guardian accounts existed keeps its * original meaning. */ public readonly int $acceptedBy = 0, public readonly ?string $ipAddress = null, public readonly ?string $acceptedAt = null, public readonly ?int $id = null, ) {} public static function fromRow( \stdClass $row ): self { return new self( policyVersionId: Val::int( $row->policy_version_id ), studentId: Val::int( $row->student_id ), registrationType: Val::string( $row->registration_type ), registrationId: Val::int( $row->registration_id ), acceptedBy: Val::int( $row->accepted_by ?? 0 ), ipAddress: Val::stringOrNull( $row->ip_address ), acceptedAt: Val::stringOrNull( $row->accepted_at ), id: Val::int( $row->id ), ); } /** * Who this acceptance is legally attributable to: the recorded acceptor, * falling back to the student. Callers go through here so the `0` default of a * pre-guardian acceptance never leaks out as a user id. */ public function acceptorOrStudent(): int { return $this->acceptedBy > 0 ? $this->acceptedBy : $this->studentId; } /** * Whether someone other than the student agreed — a guardian accepting on a * child's behalf. Drives the "accepted by" line on the admin screen, which is * noise when they are the same person. */ public function acceptedOnBehalf(): bool { return $this->acceptedBy > 0 && $this->acceptedBy !== $this->studentId; } /** * Returns a plain array representation of the acceptance. * * @return array */ public function toArray(): array { return [ 'id' => $this->id, 'policy_version_id' => $this->policyVersionId, 'student_id' => $this->studentId, 'accepted_by' => $this->acceptorOrStudent(), 'registration_type' => $this->registrationType, 'registration_id' => $this->registrationId, 'ip_address' => $this->ipAddress, 'accepted_at' => $this->acceptedAt, ]; } }