XF 2.2 Using a new entity's id on creation

Taylor J

Well-known member
I'm attempting to set an sql field on an entity to the location of an image inside of XF's data dir at the time when the creation/save process happens but I'm not quite sure how to approach this.

I am able to get the file saved with the proper filename and in the proper place (data://taylorj_blogs/blog_header_image/filenamehere.jpg) where filename is the id of the entity that is/was created.

I've tried placing my code in my saveProcess method as seen below
PHP:
protected function blogSaveProcess(\TaylorJ\Blogs\Entity\Blog $blog)
    {
        $input = $this->filter([
            'blog_title' => 'str',
            'blog_description' => 'str',
        ]);

        $form = $this->formAction();
        $form->basicEntitySave($blog, $input);



        return $form;
    }
but since the entity hasn't been created/saved yet it doesn't have an actual "blog_id" yet and every path is set to data://taylorj_blogs/blog_header_image/0.jpg.

To save the image with the actual blog_id I have my code to save the image in the actionSave method below

PHP:
public function actionSave(ParameterBag $params)
    {
        if ($params->blog_id)
        {
            $blog = $this->assertBlogExists($params->blog_id);
        }
        else
        {
            $blog = $this->em()->create('TaylorJ\Blogs:Blog');
        }

        $this->blogSaveProcess($blog)->run();

        if ($upload = $this->request->getFile('upload', false, false)) {
            $this->getBlogRepo()->setBlogHeaderImagePath($blog->blog_id, $upload);
        }

        return $this->redirect($this->buildLink('blogs'));
    }
but it doesn't feel or seem right to try and attempt to update a entity's sql fields here in this area.
Am I even headed in the right direction with this? I feel like i'm overcomplicating it.
 
I was able to solve this by adding the following to the bottom of my actionSave method right before the redirect from the post above

PHP:
$blog->blog_header_image = 'data/taylorj_blogs/blog_header_images/'.$blog->blog_id.'.jpg';
$blog->save();
 
Last edited:
Doing a full save like that will work, but it's not going to be particularly efficient.

For things like that, you would normally use the fastUpdate() method in the entity. If you look in the XF\Service\Post\Preparer class, you can see how associated record info is updated in the post table after the post is created (check out the associateAttachments() and writeIpLog() methods). One updates the attach_count (you have to have the post_id before you can associate those attachments to that post, but then you want to keep a counter of how many attachments there are in the post record. Same concept with logging the IP address. You need the post_id to create the IP record, but then you also want the original post to have the ip_id of the IP log.
 
You can also use a getter rather than a real column if the data is simply derived from other columns:

PHP:
public function getBlogHeaderImage(): string
{
    return "data/taylorj_blogs/blog_header_images/{$this->blog_id}.jpg";
}

// ...

public static function getStructure(Structure $structure)
{
    // ...
    $structure->getters['blog_header_image'] = true;
    // ...
}

For files stored in in the data directory, it's a good idea to use \XF\App::applyExternalDataUrl since the URL may be configured to serve from a CDN:

PHP:
public function getBlogHeaderImage(bool $canonical = false): string
{
    return $this->app()->applyExternalDataUrl(
        "taylorj_blogs/blog_header_images/{$this->blog_id}.jpg",
        $canonical
    );
}
 
Thank you both @digitalpoint and @Jeremy P. I had a feeling it wasn't the proper way to handle it but wanted to get it working in a rush.

For files stored in in the data directory, it's a good idea to use \XF\App::applyExternalDataUrl since the URL may be configured to serve from a CDN:

PHP:
public function getBlogHeaderImage(bool $canonical = false): string
{
    return $this->app()->applyExternalDataUrl(
        "taylorj_blogs/blog_header_images/{$this->blog_id}.jpg",
        $canonical
    );
}
This actually saved me time by answering another question I was going to end up posting here! I had at seen some other areas use a "data://filelocation" solution and then using base_url() in the template but I wasn't able to get the images to show up that way so went with what I had above.
 
You could also leverage the complete() to do the attachment stuff


PHP:
protected function blogSaveProcess(\TaylorJ\Blogs\Entity\Blog $blog)
{
    $input = $this->filter([
        'blog_title' => 'str',
        'blog_description' => 'str',
    ]);

    $form = $this->formAction();
    $form->basicEntitySave($blog, $input);
   
    $form->complete(function() use ($blog)
    {
        // put your attachment code here
        // $blog->id will be present
    });


    return $form;
}
 
Top Bottom