src/App/Entity/System/Twilio/TwilioConfig.php line 23

Open in your IDE?
  1. <?php
  2. namespace App\Entity\System\Twilio;
  3. use App\Entity\System\AbstractMessagingConfig;
  4. use App\Entity\System\Twilio\TwilioConfigs;
  5. use Cms\CoreBundle\Model\Interfaces\Identifiable\IdentifiableInterface;
  6. use Doctrine\ORM\Mapping as ORM;
  7. use JsonSerializable;
  8. use Ramsey\Uuid\UuidInterface;
  9. /**
  10.  * Class TwilioConfig
  11.  * @package App\Entity\System\Twilio
  12.  *
  13.  * @ORM\Entity(
  14.  *     repositoryClass = "App\Doctrine\Repository\System\Twilio\TwilioConfigRepository",
  15.  * )
  16.  * @ORM\Table(
  17.  *     name = "sys__twilio_config",
  18.  * )
  19.  */
  20. class TwilioConfig extends AbstractMessagingConfig implements JsonSerializable
  21. {
  22.     /** @see https://www.twilio.com/docs/sms/a2p-10dlc/onboarding-isv-api?code-sample=code-fetch-possible-a2p-campaign-use-cases&code-language=PHP&code-sdk-version=6.x */
  23.     const SMS_CAMPAIGN_USE_CASE__DEFAULT 'MIXED';
  24.     const SMS_CAMPAIGN_USE_CASE__501C3 'CHARITY';
  25.     const SMS_CAMPAIGN_DESCRIPTION 'Urgent messages related to school closures/lockdowns, health/safety issues, event/fundraiser reminders, account maintenance notifications, and OTP.';
  26.     const SMS_CAMPAIGN_MESSAGE_FLOW 'Our customers (K-12 schools/districts and secondary education organizations) provide phone numbers of parents and staff for communication purposes. Pursuant to FCC ruling 16-88 (https://docs.fcc.gov/public/attachments/FCC-16-88A1.pdf), these contacts are auto-enrolled in "emergency" communications (school closures, lockdowns, etc). For non-emergency communication, users must sign into a web portal or utilize our mobile app to further opt-in to such non-emergency SMS communications. Users can opt-out of any kind of communication (emergency or non-emergency) by utilizing the web portal, mobile app, by replying with STOP to the number they receive the communication from, or by contacting a school administrator who performs the opt-out on the user\'s behalf via our administrative dashboard.';
  27.     const SMS_CAMPAIGN_SAMPLES = [
  28.         'LCSC: School campuses are closed Wed. Aug. 9 due to the city snow emergency. It will become an e-learning day. Teachers do not report. https://cmps.st/12345678',
  29.         'GMS: 7th and 8th Grade volleyball is tonight @Jac-Cen-Del at 5:30pm. Thursday @Batesville starts at 6pm. https://cmps.st/12345678',
  30.         'Welcome to the 2021-22 school year! Use the following link to set your preferences on how to receive school notifications. https://cmps.st/12345678',
  31.         'SchoolNow: You requested to manage contact info. Your login code expires in 15 minutes and is: 123456. Do NOT share this code; we will NEVER call to ask for it.',
  32.     ];
  33.     const SMS_CAMPAIGN_USES_LINKS true;
  34.     const SMS_CAMPAIGN_USES_PHONES true;
  35.     use TwilioConfigs\TwilioConfigSubaccountTrait;
  36.     use TwilioConfigs\TwilioConfigApiTrait;
  37.     use TwilioConfigs\TwilioConfigInfoTrait;
  38.     use TwilioConfigs\TwilioConfigBusinessTrait;
  39.     use TwilioConfigs\TwilioConfigPhoneTrait;
  40.     use TwilioConfigs\TwilioConfigCallerIdTrait;
  41.     use TwilioConfigs\TwilioConfigMessagingTrait;
  42.     use TwilioConfigs\TwilioConfigSmsTrait;
  43.     use TwilioConfigs\TwilioConfigVoiceTrait;
  44.     use TwilioConfigs\TwilioConfigCnamTrait;
  45.     /**
  46.      *
  47.      */
  48.     public function __construct()
  49.     {
  50.         $this->__constructSubaccount();
  51.         $this->__constructApi();
  52.         $this->__constructInfo();
  53.         $this->__constructBusiness();
  54.         $this->__constructPhone();
  55.         $this->__constructCallerId();
  56.         $this->__constructMessaging();
  57.         $this->__constructSms();
  58.         $this->__constructVoice();
  59.         $this->__constructCnam();
  60.     }
  61.     /**
  62.      * @param array $evaluation
  63.      * @return array|string[]
  64.      */
  65.     protected function reduceEvaluation(array $evaluation): array
  66.     {
  67.         $errors = [];
  68.         if ($evaluation['status'] !== 'compliant') {
  69.             foreach ($evaluation['results'] as $result) {
  70.                 if ( ! $result['passed']) {
  71.                     if ($result['failure_reason']) {
  72.                         $errors[] = sprintf(
  73.                             '%s [%s]: %s',
  74.                             $result['requirement_friendly_name'],
  75.                             $result['error_code'],
  76.                             $result['failure_reason']
  77.                         );
  78.                     }
  79.                     foreach ($result['fields'] as $field) {
  80.                         if ( ! $field['passed']) {
  81.                             $errors[] = sprintf(
  82.                                 '%s | %s [%s]: %s',
  83.                                 $result['requirement_friendly_name'],
  84.                                 $field['friendly_name'],
  85.                                 $field['error_code'],
  86.                                 $field['failure_reason']
  87.                             );
  88.                         }
  89.                     }
  90.                 }
  91.             }
  92.         }
  93.         return $errors;
  94.     }
  95.     /**
  96.      * @return bool
  97.      */
  98.     public function isSmsUsable(): bool
  99.     {
  100.         // we must be provisioned and have a phone number present in order to be usable
  101.         return (
  102.                 $this->isProvisioned()
  103.             &&
  104.                 $this->getIncomingPhoneNumberSid()
  105.         );
  106.     }
  107.     /**
  108.      * @return bool
  109.      */
  110.     public function isVoiceUsable(): bool
  111.     {
  112.         // we must be provisioned and have a phone number present in order to be usable
  113.         return (
  114.                 $this->isProvisioned()
  115.             &&
  116.                 $this->getIncomingPhoneNumberSid()
  117.         );
  118.     }
  119.     /**
  120.      * {@inheritDoc}
  121.      */
  122.     public function isUsable(): bool
  123.     {
  124.         return (
  125.                 $this->isSmsUsable()
  126.             &&
  127.                 $this->isVoiceUsable()
  128.         );
  129.     }
  130.     /**
  131.      * Mainly used to determine if we are able to properly search for phone numbers for this customer.
  132.      *
  133.      * @return bool
  134.      */
  135.     public function isLocateable(): bool
  136.     {
  137.         return (
  138.                 $this->getBusinessInfo()->getPhone()
  139.             &&
  140.                 $this->getAddressInfo()->getCountry()
  141.         );
  142.     }
  143.     /**
  144.      * {@inheritDoc}
  145.      */
  146.     public function isCredentialed(): bool
  147.     {
  148.         // all we need is a subaccount setup along with api creds
  149.         // we can assume if we have an api key id, that we have the secret and stuff too...
  150.         return (
  151.                 $this->getSubaccountSid()
  152.             &&
  153.                 $this->getApiKeySid()
  154.         );
  155.     }
  156.     /**
  157.      * {@inheritDoc}
  158.      */
  159.     public function isProvisioned(): bool
  160.     {
  161.         // all we need is a subaccount setup along with api creds
  162.         // we can assume if we have an api key id, that we have the secret and stuff too...
  163.         return (
  164.                 $this->isCredentialed()
  165.             &&
  166.                 $this->getTwimlAppSid()
  167.             &&
  168.                 $this->getMessagingServiceSid()
  169.         );
  170.     }
  171.     /**
  172.      * {@inheritDoc}
  173.      */
  174.     public function canOrganizationRegister(): bool
  175.     {
  176.         // check the parent requirements first
  177.         $check parent::canOrganizationRegister();
  178.         // only check the other things if we passed the parent test
  179.         if ($check) {
  180.             // must loop over all the information collectors
  181.             // if any one of these is not registerable, it fails the whole check
  182.             foreach ($this->getInfos() as $info) {
  183.                 if ( ! $info->isRegisterable()) {
  184.                     $check false;
  185.                     break;
  186.                 }
  187.             }
  188.         }
  189.         return $check;
  190.     }
  191.     /**
  192.      * {@inheritDoc}
  193.      */
  194.     public function isOrganizationRegistered(): bool
  195.     {
  196.         return (
  197.                 $this->getBusinessProfileSid()
  198.             &&
  199.                 $this->getAssignmentBusinessProfileSid()
  200.             &&
  201.                 $this->getAddressSid()
  202.             &&
  203.                 $this->getSupportingDocumentSid()
  204.             &&
  205.                 $this->getAssignmentSupportingDocumentSid()
  206.             &&
  207.                 $this->getBusinessInformationSid()
  208.             &&
  209.                 $this->getAssignmentBusinessInformationSid()
  210.             &&
  211.                 $this->getPrimaryRepresentativeSid()
  212.             &&
  213.                 $this->getAssignmentPrimaryRepresentativeSid()
  214.             &&
  215.                 $this->getSecondaryRepresentativeSid()
  216.             &&
  217.                 $this->getAssignmentSecondaryRepresentativeSid()
  218.             &&
  219.                 $this->getAssignmentPhoneChannelEndpointSids()
  220.             &&
  221.                 $this->checksum() === $this->getPersistedChecksum()
  222.         );
  223.     }
  224.     /**
  225.      * {@inheritDoc}
  226.      */
  227.     public function isOrganizationEvaluated(): bool
  228.     {
  229.         return (
  230.                 $this->isOrganizationRegistered()
  231.             &&
  232.                 $this->getEvaluationStatus() === 'compliant'
  233.         );
  234.     }
  235.     /**
  236.      * {@inheritDoc}
  237.      */
  238.     public function isOrganizationSubmitted(): bool
  239.     {
  240.         return (
  241.                 $this->isOrganizationRegistered()
  242.             &&
  243.                 $this->getStatus()
  244.             &&
  245.                 $this->getStatus() !== 'draft'
  246.         );
  247.     }
  248.     /**
  249.      * {@inheritDoc}
  250.      */
  251.     public function isOrganizationVerified(): bool
  252.     {
  253.         return (
  254.                 $this->isOrganizationRegistered()
  255.             &&
  256.                 in_array($this->getStatus(), ['twilio-approved','twilio-rejected'])
  257.         );
  258.     }
  259.     /**
  260.      * {@inheritDoc}
  261.      */
  262.     public function isOrganizationValid(): bool
  263.     {
  264.         return (
  265.                 $this->isOrganizationVerified()
  266.             &&
  267.                 $this->getStatus() === 'twilio-approved'
  268.         );
  269.     }
  270.     /**
  271.      * {@inheritDoc}
  272.      */
  273.     public function isOrganizationInvalid(): bool
  274.     {
  275.         return (
  276.                 $this->isOrganizationVerified()
  277.             &&
  278.                 $this->getStatus() === 'twilio-rejected'
  279.         );
  280.     }
  281.     /**
  282.      * {@inheritDoc}
  283.      */
  284.     public function canPrepare(): bool
  285.     {
  286.         return (
  287.                 $this->isProvisioned()
  288.             &&
  289.                 $this->isLocateable()
  290.         );
  291.     }
  292.     /**
  293.      * {@inheritDoc}
  294.      */
  295.     public function isPrepared(): bool
  296.     {
  297.         return $this->hasPhoneNumbers();
  298.     }
  299.     /**
  300.      * @return bool
  301.      */
  302.     public function isSmsServiceRegistered(): bool
  303.     {
  304.         return (
  305.                 $this->getSmsTrustProductSid()
  306.             &&
  307.                 $this->getSmsMessagingProfileInformationSid()
  308.             &&
  309.                 $this->getSmsAssignmentMessagingProfileInformationSid()
  310.             &&
  311.                 $this->getSmsAssignmentPrimaryTrustProductSid()
  312.             &&
  313.                 $this->getSmsAssignmentTrustProductSid()
  314.         );
  315.     }
  316.     /**
  317.      * @return bool
  318.      */
  319.     public function isVoiceServiceRegistered(): bool
  320.     {
  321.         return (
  322.                 $this->getVoiceTrustProductSid()
  323.             &&
  324.                 $this->getVoiceAssignmentTrustProductSid()
  325.             &&
  326.                 $this->getVoiceAssignmentPrimaryTrustProductSid()
  327.             &&
  328.                 (count($this->getVoiceAssignmentChannelEndpointSids()) === count($this->getIncomingPhoneNumberSids()))
  329.         );
  330.     }
  331.     /**
  332.      * @return bool
  333.      */
  334.     public function isCnamServiceRegistered(): bool
  335.     {
  336.         return (
  337.                 $this->getCnamTrustProductSid()
  338.             &&
  339.                 $this->getCnamAssignmentTrustProductSid()
  340.             &&
  341.                 $this->getCnamAssignmentPrimaryTrustProductSid()
  342.             &&
  343.                 $this->getCnamInformationSid()
  344.             &&
  345.                 $this->getCnamAssignmentInformationSid()
  346.             &&
  347.                 (count($this->getCnamAssignmentChannelEndpointSids()) === count($this->getIncomingPhoneNumberSids()))
  348.         );
  349.     }
  350.     /**
  351.      * {@inheritDoc}
  352.      */
  353.     public function isServicesRegistered(): bool
  354.     {
  355.         return (
  356.                 $this->isSmsServiceRegistered()
  357.             &&
  358.                 $this->isVoiceServiceRegistered()
  359.             &&
  360.                 $this->isCnamServiceRegistered()
  361.         );
  362.     }
  363.     /**
  364.      * @return bool
  365.      */
  366.     public function isSmsServiceEvaluated(): bool
  367.     {
  368.         return ($this->getSmsEvaluationStatus() === 'compliant');
  369.     }
  370.     /**
  371.      * @return bool
  372.      */
  373.     public function isVoiceServiceEvaluated(): bool
  374.     {
  375.         return ($this->getVoiceEvaluationStatus() === 'compliant');
  376.     }
  377.     /**
  378.      * @return bool
  379.      */
  380.     public function isCnamServiceEvaluated(): bool
  381.     {
  382.         return ($this->getCnamEvaluationStatus() === 'compliant');
  383.     }
  384.     /**
  385.      * {@inheritDoc}
  386.      */
  387.     public function isServicesEvaluated(): bool
  388.     {
  389.         return (
  390.                 $this->isSmsServiceEvaluated()
  391.             &&
  392.                 $this->isVoiceServiceEvaluated()
  393.             &&
  394.                 $this->isCnamServiceEvaluated()
  395.         );
  396.     }
  397.     /**
  398.      * @return bool
  399.      */
  400.     public function isSmsServiceSubmitted(): bool
  401.     {
  402.         return in_array($this->getSmsStatus(), ['pending-review','in-review']);
  403.     }
  404.     /**
  405.      * @return bool
  406.      */
  407.     public function isVoiceServiceSubmitted(): bool
  408.     {
  409.         return in_array($this->getVoiceStatus(), ['pending-review','in-review']);
  410.     }
  411.     /**
  412.      * @return bool
  413.      */
  414.     public function isCnamServiceSubmitted(): bool
  415.     {
  416.         return in_array($this->getCnamStatus(), ['pending-review','in-review']);
  417.     }
  418.     /**
  419.      * {@inheritDoc}
  420.      */
  421.     public function isServicesSubmitted(): bool
  422.     {
  423.         return (
  424.                 $this->isSmsServiceSubmitted()
  425.             &&
  426.                 $this->isVoiceServiceSubmitted()
  427.             &&
  428.                 $this->isCnamServiceSubmitted()
  429.         );
  430.     }
  431.     /**
  432.      * @return bool
  433.      */
  434.     public function isSmsServiceVerified(): bool
  435.     {
  436.         return in_array($this->getSmsStatus(), ['twilio-approved','twilio-rejected']);
  437.     }
  438.     /**
  439.      * @return bool
  440.      */
  441.     public function isVoiceServiceVerified(): bool
  442.     {
  443.         return in_array($this->getVoiceStatus(), ['twilio-approved','twilio-rejected']);
  444.     }
  445.     /**
  446.      * @return bool
  447.      */
  448.     public function isCnamServiceVerified(): bool
  449.     {
  450.         return in_array($this->getCnamStatus(), ['twilio-approved','twilio-rejected']);
  451.     }
  452.     /**
  453.      * {@inheritDoc}
  454.      */
  455.     public function isServicesVerified(): bool
  456.     {
  457.         return (
  458.                 $this->isSmsServiceVerified()
  459.             &&
  460.                 $this->isVoiceServiceVerified()
  461.             &&
  462.                 $this->isCnamServiceVerified()
  463.         );
  464.     }
  465.     /**
  466.      * @return bool
  467.      */
  468.     public function isSmsServiceValid(): bool
  469.     {
  470.         return ($this->getSmsStatus() === 'twilio-approved');
  471.     }
  472.     /**
  473.      * @return bool
  474.      */
  475.     public function isVoiceServiceValid(): bool
  476.     {
  477.         return ($this->getVoiceStatus() === 'twilio-approved');
  478.     }
  479.     /**
  480.      * @return bool
  481.      */
  482.     public function isCnamServiceValid(): bool
  483.     {
  484.         return ($this->getCnamStatus() === 'twilio-approved');
  485.     }
  486.     /**
  487.      * {@inheritDoc}
  488.      */
  489.     public function isServicesValid(): bool
  490.     {
  491.         return (
  492.                 $this->isSmsServiceValid()
  493.             &&
  494.                 $this->isVoiceServiceValid()
  495.             &&
  496.                 $this->isCnamServiceValid()
  497.         );
  498.     }
  499.     /**
  500.      * @return bool
  501.      */
  502.     public function isSmsServiceInvalid(): bool
  503.     {
  504.         return ($this->getSmsStatus() === 'twilio-rejected');
  505.     }
  506.     /**
  507.      * @return bool
  508.      */
  509.     public function isVoiceServiceInvalid(): bool
  510.     {
  511.         return ($this->getVoiceStatus() === 'twilio-rejected');
  512.     }
  513.     /**
  514.      * @return bool
  515.      */
  516.     public function isCnamServiceInvalid(): bool
  517.     {
  518.         return ($this->getCnamStatus() === 'twilio-rejected');
  519.     }
  520.     /**
  521.      * {@inheritDoc}
  522.      */
  523.     public function isServicesInvalid(): bool
  524.     {
  525.         return (
  526.                 $this->isSmsServiceInvalid()
  527.             ||
  528.                 $this->isVoiceServiceInvalid()
  529.             ||
  530.                 $this->isCnamServiceInvalid()
  531.         );
  532.     }
  533.     /**
  534.      * {@inheritDoc}
  535.      */
  536.     public function canBrandSubmit(): bool
  537.     {
  538.         return (
  539.                $this->isSmsServiceSubmitted()
  540.             ||
  541.                $this->isSmsServiceValid()
  542.         );
  543.     }
  544.     /**
  545.      * {@inheritDoc}
  546.      */
  547.     public function isBrandSubmitted(): bool
  548.     {
  549.         // as soon as you create the brand, it is submitted
  550.         // TODO: we can check for PENDING status, but not really needed?
  551.         return boolval($this->getSmsBrandRegistrationSid());
  552.     }
  553.     /**
  554.      * {@inheritDoc}
  555.      */
  556.     public function isBrandVerified(): bool
  557.     {
  558.         return in_array($this->getSmsBrandRegistrationStatus(), ['APPROVED','FAILED']);
  559.     }
  560.     /**
  561.      * {@inheritDoc}
  562.      */
  563.     public function isBrandValid(): bool
  564.     {
  565.         return ($this->getSmsBrandRegistrationStatus() === 'APPROVED');
  566.     }
  567.     /**
  568.      * {@inheritDoc}
  569.      */
  570.     public function isBrandInvalid(): bool
  571.     {
  572.         return ($this->getSmsBrandRegistrationStatus() === 'FAILED');
  573.     }
  574.     /**
  575.      * {@inheritDoc}
  576.      */
  577.     public function canCampaignsSubmit(): bool
  578.     {
  579.         return $this->isBrandValid();
  580.     }
  581.     /**
  582.      * {@inheritDoc}
  583.      */
  584.     public function isCampaignsSubmitted(): bool
  585.     {
  586.         // as soon as you create the campaign, it is submitted
  587.         return boolval($this->getSmsCampaignSid());
  588.     }
  589.     /**
  590.      * {@inheritDoc}
  591.      */
  592.     public function isCampaignsVerified(): bool
  593.     {
  594.         return in_array($this->getSmsCampaignStatus(), ['VERIFIED','FAILED']);
  595.     }
  596.     /**
  597.      * {@inheritDoc}
  598.      */
  599.     public function isCampaignsValid(): bool
  600.     {
  601.         return ($this->getSmsCampaignStatus() === 'VERIFIED');
  602.     }
  603.     /**
  604.      * {@inheritDoc}
  605.      */
  606.     public function isCampaignsInvalid(): bool
  607.     {
  608.         return ($this->getSmsCampaignStatus() === 'FAILED');
  609.     }
  610.     /**
  611.      * @return bool
  612.      */
  613.     public function isSetup(): bool
  614.     {
  615.         return ($this->getTwimlAppSid() && $this->getMessagingServiceSid());
  616.     }
  617.     /**
  618.      * @return bool
  619.      */
  620.     public function isConfigured(): bool
  621.     {
  622.         return boolval($this->getSmsCampaignSid());
  623.     }
  624.     /**
  625.      * @return bool
  626.      */
  627.     public function isSmsReady(): bool
  628.     {
  629.         return (
  630.                 $this->isProvisioned()
  631.             &&
  632.                 $this->isOrganizationValid()
  633.             &&
  634.                 $this->isSmsServiceValid()
  635.             &&
  636.                 $this->isBrandValid()
  637.             &&
  638.                 $this->isCampaignsValid()
  639.         );
  640.     }
  641.     /**
  642.      * @return bool
  643.      */
  644.     public function isVoiceReady(): bool
  645.     {
  646.         return (
  647.                 $this->isProvisioned()
  648.             &&
  649.                 $this->isOrganizationValid()
  650.             &&
  651.                 $this->isVoiceServiceValid()
  652.         );
  653.     }
  654.     /**
  655.      * @return string
  656.      */
  657.     public function checksum(): string
  658.     {
  659.         return md5(implode('|', [
  660.             $this->getBusinessInfo()->checksum(),
  661.             $this->getAddressInfo()->checksum(),
  662.             $this->getPrimaryContactInfo()->checksum(),
  663.             $this->getSecondaryContactInfo()->checksum(),
  664.         ]));
  665.     }
  666.     /**
  667.      * @return string
  668.      */
  669.     public function checksumPayload(): string
  670.     {
  671.         return implode('|', [
  672.             $this->getBusinessInfo()->checksumPayload(),
  673.             $this->getAddressInfo()->checksumPayload(),
  674.             $this->getPrimaryContactInfo()->checksumPayload(),
  675.             $this->getSecondaryContactInfo()->checksumPayload(),
  676.         ]);
  677.     }
  678.     /**
  679.      * @return array|array[]
  680.      */
  681.     public function statuses(): array
  682.     {
  683.         return [
  684.             'business' => [
  685.                 'status' => $this->getStatus(),
  686.                 'timestamp' => $this->getStatusTimestamp(),
  687.             ],
  688.             'sms.a2p-10dlc' => [
  689.                 'status' => $this->getSmsStatus(),
  690.                 'timestamp' => $this->getSmsStatusTimestamp(),
  691.             ],
  692.             'sms.brand' => [
  693.                 'status' => $this->getSmsBrandRegistrationStatus(),
  694.                 'timestamp' => $this->getSmsBrandRegistrationStatusTimestamp(),
  695.             ],
  696.             'sms.campaign' => [
  697.                 'status' => $this->getSmsCampaignStatus(),
  698.                 'timestamp' => $this->getSmsCampaignStatusTimestamp(),
  699.             ],
  700.             'voice.shaken-stir' => [
  701.                 'status' => $this->getVoiceStatus(),
  702.                 'timestamp' => $this->getVoiceStatusTimestamp(),
  703.             ],
  704.             'voice.cnam' => [
  705.                 'status' => $this->getCnamStatus(),
  706.                 'timestamp' => $this->getCnamStatusTimestamp(),
  707.             ],
  708.         ];
  709.     }
  710.     /**
  711.      * {@inheritDoc}
  712.      */
  713.     public function jsonSerialize(): array
  714.     {
  715.         static $refl null;
  716.         $formatter = static function ($value, callable $formatter) {
  717.             switch (true) {
  718.                 case is_scalar($value):
  719.                     return $value;
  720.                 case is_array($value):
  721.                     $data = [];
  722.                     foreach ($value as $k => $v) {
  723.                         $data[$k] = $formatter($v$formatter);
  724.                     }
  725.                     ksort($data);
  726.                     return $data;
  727.                 case $value instanceof UuidInterface:
  728.                     return $value->__toString();
  729.                 case $value instanceof IdentifiableInterface:
  730.                     return $value->getId();
  731.                 case $value instanceof \DateTimeInterface:
  732.                     return $value->format(\DateTimeInterface::RFC3339);
  733.                 case $value instanceof JsonSerializable:
  734.                     return $value->jsonSerialize();
  735.                 default:
  736.                     return null;
  737.             }
  738.         };
  739.         if ( ! $refl) {
  740.             $refl = new \ReflectionObject($this);
  741.         }
  742.         $data = [];
  743.         foreach ($refl->getProperties(\ReflectionProperty::IS_PROTECTED) as $property) {
  744.             $property->setAccessible(true);
  745.             $data[$property->getName()] = $formatter(
  746.                 $property->getValue($this),
  747.                 $formatter
  748.             );
  749.             $property->setAccessible(false);
  750.         }
  751.         ksort($data);
  752.         return $data;
  753.     }
  754. }