Answers.Body: <p>Assuming the Pass bundle is created in a <a href="https://craftcms.com/docs/4.x/extend/services.html" rel="nofollow noreferrer">service</a>, your controller should only be responsible for calling that method and preparing a response. As @MoritzLost said, setting headers from a service method (or allowing another library to control the HTTP response) is generally inadvisable—in part because those tools exist in Craft/Yii, and in part because you should be able to create a Pass and ZIP file without caring about when or where it will be used (say, precompiling a bundle in a background job or building one to be attached to an email).</p>
  <p>To that end, your service might look like this:</p>
  <pre class="lang-php prettyprint-override"><code>namespace mynamespace\myplugin\services;
  
  use craft\base\Component;
  use PKPass\PKPass;
  
  class Passes extends Component
  {
      public function createPass(): string
      {
          $pass = new PKPass('...', '...');
          $pass-&gt;setData([
              // ...
          ]);
  
          // Return the ZIP’s content, directly:
          return $pass-&gt;create(false);
      }
  }
  </code></pre>
  <p>(This method could be parameterized so that it can look up an entry or whatever other info you need to create a unique pass!)</p>
  <p>Your controller would look like this:</p>
  <pre class="lang-php prettyprint-override"><code>namespace mynamespace\myplugin\controllers;
  
  use craft\web\Controller;
  use mynamespace\myplugin\Plugin;
  
  class PassController extends Controller
  {
      public function actionGetPass(int $id)
      {
          // Call service to create pass:
          $pass = Plugin::getInstance()-&gt;getPasses()-&gt;createPass($someArg);
  
          // Send the file’s contents:
          return $this-&gt;response-&gt;sendContentAsFile($pass, 'Pass File Name.pkpass', [
              'mimeType' =&gt; 'application/vnd.apple.pkpass',
          ]);
      }
  }
  </code></pre>
  <p>We're using the built-in <a href="https://www.yiiframework.com/doc/api/2.0/yii-web-response#sendContentAsFile()-detail" rel="nofollow noreferrer"><code>sendContentAsFile()</code></a> response method via the controller’s <code>response</code> property, and declaring the appropriate MIME type so that the client has a hint about what to do with the attachment.</p>
  <p>Read more about sending files in the Yii documentation on <a href="https://www.yiiframework.com/doc/guide/2.0/en/runtime-responses#sending-files" rel="nofollow noreferrer">handling requests</a>!</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2023-07-01T00:45:42.720Z
Answers.Text: Thanks August, this is great (and a good reminder of how much I still have to learn.) I think I'm close, but unsure on the `$this->sendContentAsFile` part. With that in place, I get a 'calling unknown method' error. Excuse my lack of knowledge, but how could I make that work?
Answers.UserDisplayName: supazu
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2023-07-02T06:05:36.960Z
Answers.Text: Looks like I needed `$this->response->sendContentAsFile()` - got it working now! Thank you!!
Answers.UserDisplayName: supazu
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2023-07-03T15:06:54.113Z
Answers.Text: Ah, shoot, you're totally right. Updating the answer accordingly!
Answers.UserDisplayName: August Miller
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2023-06-30T16:32:41.417Z
Answers.LastActivityDate: 2023-07-03T15:08:11.880Z
Answers.LastEditDate: 2023-07-03T15:08:11.880Z
Answers.UserDisplayName: August Miller
Body: <p>I'm trying to create a controller for a user to download a membership card as a pass in Apple Wallet. I'm looking to use the <a href="https://github.com/includable/php-pkpass/tree/master" rel="nofollow noreferrer">PKPass Library</a>.</p>
  <p>I've got things set up and working using their example like this:</p>
  <pre><code>$pass = new PKPass(Yii::getAlias('path/to/cert.p12'), 'myPassword');
  $data = [
    'description' =&gt; 'Membership Card',
    // etc as per example: https://github.com/includable/php-pkpass/blob/master/examples/example.php
  ];
  
  $pass-&gt;setData($data);
  
  $pass-&gt;create(true);
  </code></pre>
  <p>but get a 'Headers already sent' error when trying to download the pass. I feel like this is because the PKPass class is outputting to the browser before my module has finished processing the request.</p>
  <p>So changing the last line to <code>$createdPass = $pass-&gt;create(false);</code> and then handling the output is probably what I want to do, but I'm stuck on how to get that <code>$zip</code> response back into my controller as a file to download. Here is the <code>create</code> method:</p>
  <pre><code>public function create($output = false)
  {
      // Prepare payload
      $manifest = $this-&gt;createManifest();
      $signature = $this-&gt;createSignature($manifest);
  
      // Build ZIP file
      $zip = $this-&gt;createZip($manifest, $signature);
  
      // Return pass
      if (!$output) {
          return $zip;
      }
  
      // Output pass
      header('Content-Description: File Transfer');
      header('Content-Type: application/vnd.apple.pkpass');
      header('Content-Disposition: attachment; filename=&quot;' . $this-&gt;getName() . '&quot;');
      header('Content-Transfer-Encoding: binary');
      header('Connection: Keep-Alive');
      header('Expires: 0');
      header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
      header('Last-Modified: ' . gmdate('D, d M Y H:i:s T'));
      header('Pragma: public');
      echo $zip;
  
      return '';
  }
  </code></pre>
  <p>This might be more of a basic PHP question, but does anyone know how I could do that?</p>
  
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2023-06-30T07:39:49.880Z
Comments.Text: Not sure what you're missing here – if `$output` is `false`, you're already returning the `$zip`, so the controller can output it. What is not working? As a sidenote, setting headers and outputting content should not happen in a utility method, it should happen in the controller.
Comments.UserDisplayName: MoritzLost
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2023-07-01T00:56:17.880Z
Comments.Text: Thanks for the response. Whenever I see you respond to one of my questions I always get excited because I know it's going to be helpful! (I mean that sincerely) And indeed this is a good thing that I can't answer very well. I was just getting a "Safari can't download this file" error with the previous code. August's reply has helped push me in the right direction. Thanks again for the reply!
Comments.UserDisplayName: supazu
Tags: <modules>
Title: Customer Controller and downloading a file (Apple Wallet - using PKPass library)
ThreadText: # Customer Controller and downloading a file (Apple Wallet - using PKPass library)
  
  (Asked by: username_0 on 2023-06-30)
  
  I'm trying to create a controller for a user to download a membership card as a pass in Apple Wallet. I'm looking to use the [PKPass Library](https://github.com/includable/php-pkpass/tree/master).
  
  I've got things set up and working using their example like this:
  
  ```
  $pass = new PKPass(Yii::getAlias('path/to/cert.p12'), 'myPassword');
  $data = [
    'description' => 'Membership Card',
    // etc as per example: https://github.com/includable/php-pkpass/blob/master/examples/example.php
  ];
  
  $pass->setData($data);
  
  $pass->create(true);
  
  ```
  
  but get a 'Headers already sent' error when trying to download the pass. I feel like this is because the PKPass class is outputting to the browser before my module has finished processing the request.
  
  So changing the last line to `$createdPass = $pass->create(false);` and then handling the output is probably what I want to do, but I'm stuck on how to get that `$zip` response back into my controller as a file to download. Here is the `create` method:
  
  ```
  public function create($output = false)
  {
      // Prepare payload
      $manifest = $this->createManifest();
      $signature = $this->createSignature($manifest);
  
      // Build ZIP file
      $zip = $this->createZip($manifest, $signature);
  
      // Return pass
      if (!$output) {
          return $zip;
      }
  
      // Output pass
      header('Content-Description: File Transfer');
      header('Content-Type: application/vnd.apple.pkpass');
      header('Content-Disposition: attachment; filename="' . $this->getName() . '"');
      header('Content-Transfer-Encoding: binary');
      header('Connection: Keep-Alive');
      header('Expires: 0');
      header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
      header('Last-Modified: ' . gmdate('D, d M Y H:i:s T'));
      header('Pragma: public');
      echo $zip;
  
      return '';
  }
  
  ```
  
  This might be more of a basic PHP question, but does anyone know how I could do that?
  
  **Tags:** modules
  
  > **Comments:**
  > **username_1**: Not sure what you're missing here – if `$output` is `false`, you're already returning the `$zip`, so the controller can output it. What is not working? As a sidenote, setting headers and outputting content should not happen in a utility method, it should happen in the controller.  
  > **username_0**: Thanks for the response. Whenever I see you respond to one of my questions I always get excited because I know it's going to be helpful! (I mean that sincerely) And indeed this is a good thing that I can't answer very well. I was just getting a "Safari can't download this file" error with the previous code. username_2's reply has helped push me in the right direction. Thanks again for the reply!  
  
  ---
  
  ## 1 answers
  
  ### Answer by username_2 on 2023-06-30. Score: 3 | Accepted answer
  
  Assuming the Pass bundle is created in a [service](https://craftcms.com/docs/4.x/extend/services.html), your controller should only be responsible for calling that method and preparing a response. As @username_1 said, setting headers from a service method (or allowing another library to control the HTTP response) is generally inadvisable—in part because those tools exist in Craft/Yii, and in part because you should be able to create a Pass and ZIP file without caring about when or where it will be used (say, precompiling a bundle in a background job or building one to be attached to an email).
  
  To that end, your service might look like this:
  
  ```php
  namespace mynamespace\myplugin\services;
  
  use craft\base\Component;
  use PKPass\PKPass;
  
  class Passes extends Component
  {
      public function createPass(): string
      {
          $pass = new PKPass('...', '...');
          $pass->setData([
              // ...
          ]);
  
          // Return the ZIP’s content, directly:
          return $pass->create(false);
      }
  }
  
  ```
  
  (This method could be parameterized so that it can look up an entry or whatever other info you need to create a unique pass!)
  
  Your controller would look like this:
  
  ```php
  namespace mynamespace\myplugin\controllers;
  
  use craft\web\Controller;
  use mynamespace\myplugin\Plugin;
  
  class PassController extends Controller
  {
      public function actionGetPass(int $id)
      {
          // Call service to create pass:
          $pass = Plugin::getInstance()->getPasses()->createPass($someArg);
  
          // Send the file’s contents:
          return $this->response->sendContentAsFile($pass, 'Pass File Name.pkpass', [
              'mimeType' => 'application/vnd.apple.pkpass',
          ]);
      }
  }
  
  ```
  
  We're using the built-in [`sendContentAsFile()`](https://www.yiiframework.com/doc/api/2.0/yii-web-response#sendContentAsFile()-detail) response method via the controller’s `response` property, and declaring the appropriate MIME type so that the client has a hint about what to do with the attachment.
  
  Read more about sending files in the Yii documentation on [handling requests](https://www.yiiframework.com/doc/guide/2.0/en/runtime-responses#sending-files)!
  
  > **Comments:**
  > **username_0**: Thanks username_2, this is great (and a good reminder of how much I still have to learn.) I think I'm close, but unsure on the `$this->sendContentAsFile` part. With that in place, I get a 'calling unknown method' error. Excuse my lack of knowledge, but how could I make that work?  
  > **username_0**: Looks like I needed `$this->response->sendContentAsFile()` - got it working now! Thank you!!  
  > **username_2**: Ah, shoot, you're totally right. Updating the answer accordingly!  
  
  ---
  
  