<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>

CC BY-SA 4.0
2023-07-01T00:45:42.720Z
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?
supazu
CC BY-SA 4.0
2023-07-02T06:05:36.960Z
Looks like I needed `$this->response->sendContentAsFile()` - got it working now! Thank you!!
supazu
CC BY-SA 4.0
2023-07-03T15:06:54.113Z
Ah, shoot, you're totally right. Updating the answer accordingly!
August Miller
CC BY-SA 4.0
2023-06-30T16:32:41.417Z
2023-07-03T15:08:11.880Z
2023-07-03T15:08:11.880Z
August Miller
<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>

CC BY-SA 4.0
2023-06-30T07:39:49.880Z
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.
MoritzLost
CC BY-SA 4.0
2023-07-01T00:56:17.880Z
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!
supazu
<modules>
Customer Controller and downloading a file (Apple Wallet - using PKPass library)
# 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.
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. August'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, 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:
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:
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() 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!

> **Comments:**
> **username_0**: 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?  
> **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!  

---

