|
| 1 | +# API Channel |
| 2 | + |
| 3 | + |
| 4 | +Use the mail channel to submit structured to any kind of API you want. |
| 5 | + |
| 6 | +- [Mapping](./09_ApiChannel.md#mapping) | Map form fields to predefined api fields |
| 7 | +- [API Provider](./09_ApiChannel.md#api-provider) | Create a custom api provider |
| 8 | +- [Guard Event Listener](./09_ApiChannel.md#guard-event-listener) | Hook into api provider process |
| 9 | +- [Code Example: Trigger Value](./09_ApiChannel.md#example-trigger-value) | Dispatch Channel by given trigger |
| 10 | +- [Field Transformer](./16_FieldTransformer.md) *(Optional)* | Use a field transformer to modify single api field values |
| 11 | + |
| 12 | +## Note |
| 13 | +The FormBuilder API Channel does **not** ship preconfigured API Provider. They can be simple but also complex. But no worries, |
| 14 | +it's quite easy to integrate your own api provider, read more about it [here](./09_ApiChannel.md#api-provider) |
| 15 | + |
| 16 | +## Available Options |
| 17 | + |
| 18 | +| Name | Type | Description | |
| 19 | +|------|-------------|-------------| |
| 20 | +| Api Provider| `ApiProviderInterface` | Select your API Provider. | |
| 21 | +| Options | `mixed` | If available, various provider configuration fields | |
| 22 | + |
| 23 | +## Mapping |
| 24 | + |
| 25 | + |
| 26 | +If the API Provider supports predefined API fields, you're able to map form fields to these fields which will show up in a |
| 27 | +dropdown element. If there are no predefined API fields, you need to enter them manually. |
| 28 | + |
| 29 | +### Container "Fieldset" Mapping |
| 30 | +If an API field is assigned to the fieldset itself, the child elements will be created as an array branch. Otherwise, child |
| 31 | +elements will be assigned flat. |
| 32 | + |
| 33 | +### Container "Repeater" Mapping |
| 34 | +If no API field is assigned to the repeater itself, the child elements will be skipped. |
| 35 | + |
| 36 | +*** |
| 37 | + |
| 38 | +## API Provider |
| 39 | +Integrating an api provider is very simple. In this example, we're going to set up an API provider for MailChimp. |
| 40 | + |
| 41 | +### API Configuration Fields |
| 42 | +Every API Provider is allowed to provide custom configuration fields (see example below). This allows you to select various data |
| 43 | +for each form (like a campaign ID in MailChimp) |
| 44 | + |
| 45 | +### API Predefined Fields |
| 46 | +If the API Provider returns predefined you **must** map your form fields with these given fields. |
| 47 | + |
| 48 | +> Note! You're allowed to add a predefined only once, but you're allowed to add multiple predefined properties to a single form field! |
| 49 | +
|
| 50 | +```bash |
| 51 | +$ composer require mailchimp/marketing |
| 52 | +``` |
| 53 | + |
| 54 | +First, we need to register a new service: |
| 55 | +```yml |
| 56 | +AppBundle\FormBuilder\ApiProvider\MailChimpApiProvider: |
| 57 | + autowire: true |
| 58 | + public: false |
| 59 | + tags: |
| 60 | + - { name: form_builder.api.provider, identifier: mailchimp } |
| 61 | +``` |
| 62 | +
|
| 63 | +Then, we're going to implement the service itself: |
| 64 | +```php |
| 65 | +<?php |
| 66 | + |
| 67 | +namespace AppBundle\FormBuilder\ApiProvider; |
| 68 | + |
| 69 | +use FormBuilderBundle\Model\FormDefinitionInterface; |
| 70 | +use FormBuilderBundle\OutputWorkflow\Channel\Api\ApiData; |
| 71 | +use FormBuilderBundle\OutputWorkflow\Channel\Api\ApiProviderInterface; |
| 72 | + |
| 73 | +class MailChimpApiProvider implements ApiProviderInterface |
| 74 | +{ |
| 75 | + public function getName(): string |
| 76 | + { |
| 77 | + return 'MailChimp'; |
| 78 | + } |
| 79 | + |
| 80 | + public function getProviderConfigurationFields(FormDefinitionInterface $formDefinition): array |
| 81 | + { |
| 82 | + $mailchimp = $this->getClient(); |
| 83 | + $campaignsData = $mailchimp->campaigns->list(); |
| 84 | + |
| 85 | + $campaignStore = []; |
| 86 | + foreach ($campaignsData->campaigns as $campaign) { |
| 87 | + $campaignStore[] = [ |
| 88 | + 'value' => $campaign->id, |
| 89 | + 'label' => $campaign->settings->title |
| 90 | + ]; |
| 91 | + } |
| 92 | + |
| 93 | + return [ |
| 94 | + [ |
| 95 | + 'type' => 'text', |
| 96 | + 'label' => 'My Config', |
| 97 | + 'name' => 'myConfig', |
| 98 | + 'required' => true, |
| 99 | + ], |
| 100 | + [ |
| 101 | + 'type' => 'select', |
| 102 | + 'label' => 'Campaign', |
| 103 | + 'name' => 'campaign', |
| 104 | + 'store' => $campaignStore, |
| 105 | + 'required' => true, |
| 106 | + ] |
| 107 | + ]; |
| 108 | + } |
| 109 | + |
| 110 | + public function getPredefinedApiFields(FormDefinitionInterface $formDefinition, array $providerConfiguration): array |
| 111 | + { |
| 112 | + // maybe they will come from a remote campaign list. |
| 113 | + // just return an empty array if you don't want to provide predefined api fields. |
| 114 | + |
| 115 | + $fields = [ |
| 116 | + 'EMAIL', |
| 117 | + 'MMERGE6', |
| 118 | + 'FNAME', |
| 119 | + 'LNAME' |
| 120 | + ]; |
| 121 | + |
| 122 | + if ($providerConfiguration['campaign'] === '123') { |
| 123 | + $fields[] = 'SPECIAL_FIELD'; |
| 124 | + } |
| 125 | + |
| 126 | + return $fields; |
| 127 | + } |
| 128 | + |
| 129 | + public function process(ApiData $apiData): void |
| 130 | + { |
| 131 | + $mailchimp = $this->getClient(); |
| 132 | + $campaignId = $apiData->getProviderConfigurationNode('campaign'); |
| 133 | + |
| 134 | + $campaigns = $mailchimp->campaigns->get($campaignId); |
| 135 | + |
| 136 | + $body = [ |
| 137 | + 'status' => 'subscribed', |
| 138 | + 'email_address' => $apiData->getApiNode('EMAIL'), |
| 139 | + 'merge_fields' => $apiData->getApiNodes() |
| 140 | + ]; |
| 141 | + |
| 142 | + try { |
| 143 | + $mailchimp->lists->addListMember($campaignId, $body); |
| 144 | + } catch(\Throwable $e) { |
| 145 | + |
| 146 | + // don't forget to wrap your remote calls in a correct exception: |
| 147 | + |
| 148 | + // I. fail silently. no error will be visible to the user |
| 149 | + |
| 150 | + // II. OR: only channel should get bypassed (upcoming channels will be processed) |
| 151 | + // error will be visible to the user after workflow has been completely dispatched |
| 152 | + throw new GuardChannelException($e->getMessage()); |
| 153 | + |
| 154 | + // III. OR: workflow should not go one. cancel from now on |
| 155 | + // error will be visible to the user after workflow has been completely dispatched |
| 156 | + throw new GuardOutputWorkflowException($e->getMessage()); |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + protected function getClient() |
| 161 | + { |
| 162 | + $mailchimp = new MailchimpMarketing\ApiClient(); |
| 163 | + |
| 164 | + $mailchimp->setConfig([ |
| 165 | + 'apiKey' => 'YOUR_API_KEY', |
| 166 | + 'server' => 'YOUR_SERVER_PREFIX' |
| 167 | + ]); |
| 168 | + |
| 169 | + return $mailchimp; |
| 170 | + } |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +*** |
| 175 | + |
| 176 | +## Guard Event Listener |
| 177 | +As in every output channel, you're able to hook into the dispatch event via `OUTPUT_WORKFLOW_GUARD_SUBJECT_PRE_DISPATCH` event. |
| 178 | + |
| 179 | +```php |
| 180 | +<?php |
| 181 | + |
| 182 | +namespace AppBundle\FormBuilder; |
| 183 | + |
| 184 | +use FormBuilderBundle\FormBuilderEvents; |
| 185 | +use FormBuilderBundle\Event\OutputWorkflow\ChannelSubjectGuardEvent; |
| 186 | +use FormBuilderBundle\OutputWorkflow\Channel\Api\ApiData; |
| 187 | +use Symfony\Component\EventDispatcher\EventSubscriberInterface; |
| 188 | +use AppBundle\FormBuilder\ApiProvider\MailChimpApiProvider; |
| 189 | + |
| 190 | +class OutputWorkflowEventListener implements EventSubscriberInterface |
| 191 | +{ |
| 192 | + public static function getSubscribedEvents(): array |
| 193 | + { |
| 194 | + return [ |
| 195 | + FormBuilderEvents::OUTPUT_WORKFLOW_GUARD_SUBJECT_PRE_DISPATCH => 'checkSubject', |
| 196 | + ]; |
| 197 | + } |
| 198 | + |
| 199 | + public function checkSubject(ChannelSubjectGuardEvent $event): void |
| 200 | + { |
| 201 | + $subject = $event->getSubject(); |
| 202 | + |
| 203 | + // only apply if subject represents an ApiData instance |
| 204 | + if (!$subject instanceof ApiData) { |
| 205 | + return; |
| 206 | + } |
| 207 | + |
| 208 | + // only apply for specific api provider |
| 209 | + if( $subject->getApiProviderName() !== 'mailchimp') { |
| 210 | + return; |
| 211 | + } |
| 212 | + |
| 213 | + // different fail scenarios can be applied: |
| 214 | + |
| 215 | + $event->shouldFail('My invalid message for a specific channel! Allow further channels to pass!', true); |
| 216 | + |
| 217 | + $event->shouldFail('My invalid message! If this happens, no further channel will be executed!', false); |
| 218 | + |
| 219 | + // silently skip channel |
| 220 | + if ($subject->getProviderConfigurationNode('myConfig') === 'a special value') { |
| 221 | + $event->shouldSuspend(); |
| 222 | + return; |
| 223 | + } |
| 224 | + } |
| 225 | +} |
| 226 | +``` |
| 227 | + |
| 228 | +** |
| 229 | + |
| 230 | +## Example: Trigger Value |
| 231 | +In some scenarios, you want to trigger your API channel only, if a specific form value is given (A checkbox for example). We'll |
| 232 | +release this in a [dedicated feature](https://github.com/dachcom-digital/pimcore-formbuilder/issues/304) in upcoming versions. |
| 233 | +Until then, this can be solved by use a custom provider configuration field: |
| 234 | + |
| 235 | +### Configuration Field |
| 236 | +First, we have to add a configuration field. Let's call it `consentTriggerValue`: |
| 237 | +```php |
| 238 | +// AppBundle\FormBuilder\ApiProvider\MailChimpApiProvider |
| 239 | +public function getProviderConfigurationFields(FormDefinitionInterface $formDefinition): array |
| 240 | +{ |
| 241 | + return [ |
| 242 | + [ |
| 243 | + 'type' => 'text', |
| 244 | + 'label' => 'Consent Trigger Value', |
| 245 | + 'name' => 'consentTriggerValue', |
| 246 | + 'required' => false, |
| 247 | + ], |
| 248 | + ... |
| 249 | + ]; |
| 250 | +} |
| 251 | +``` |
| 252 | + |
| 253 | +### Trigger Field |
| 254 | +Then, we need to add a trigger field. Let's call it `CONSENT_TRIGGER`: |
| 255 | +```php |
| 256 | +// AppBundle\FormBuilder\ApiProvider\MailChimpApiProvider |
| 257 | +public function getPredefinedApiFields(FormDefinitionInterface $formDefinition, array $providerConfiguration): array |
| 258 | +{ |
| 259 | + return [ |
| 260 | + ... |
| 261 | + 'CONSENT_TRIGGER' |
| 262 | + ]; |
| 263 | +} |
| 264 | +``` |
| 265 | + |
| 266 | +### Channel Configuration |
| 267 | +Append our freshly created fields in the API output channel: |
| 268 | + |
| 269 | + |
| 270 | + |
| 271 | +### Dispatch Process |
| 272 | +And finally, check given consent value in your `process` method: |
| 273 | +```php |
| 274 | +public function process(ApiData $apiData): void |
| 275 | +{ |
| 276 | + $consentTriggerValue = $apiData->getProviderConfigurationNode('consentTriggerValue'); |
| 277 | + |
| 278 | + // configuration values are always available as strings |
| 279 | + if ($consentTriggerValue === 'true') { |
| 280 | + $consentTriggerValue = true; |
| 281 | + } |
| 282 | + |
| 283 | + // in our example we're dealing with a checkbox. |
| 284 | + // which means that it's not available as node if unchecked |
| 285 | + if (!$apiData->hasApiNode('CONSENT_TRIGGER')) { |
| 286 | + return; |
| 287 | + } |
| 288 | + |
| 289 | + // otherwise, it has to be the same value as defined in our configuration node! |
| 290 | + if ($consentTriggerValue !== $apiData->getApiNode('CONSENT_TRIGGER')) { |
| 291 | + return; |
| 292 | + } |
| 293 | + |
| 294 | + ... |
| 295 | +} |
| 296 | +``` |
| 297 | + |
| 298 | +And you're done. This API channel will only dispatch if a user gives consent to it. |
0 commit comments