简体   繁体   中英

Value of field "db_file_id" does not exist on referenced table

Value of field "..." does not exist on referenced table

Trying to use phalcon model relationships to save related models but get an error:

Value of field "db_file_id" does not exist on referenced table

Saving and processing services:

FilesService.php:

<?php

namespace Modules\Upload\Files;

use Core\Framework\Injectable;
use Modules\Upload\Files\Exceptions\FileNotSavedException;
use Utils\Files\File;

class FilesService extends Injectable {
    /**
    * @param File $file
    * @throws FileNotSavedException
    * @return DbFile
    */
    public function upload(
        File $file,
        string $subStoragePath = ''
    ): DbFile {
        $storage = $this->getStorage();
        $newSubStoragePath = $file->getName();

        if (!empty($subStoragePath)) {
            $newSubStoragePath = $subStoragePath;
        }

        $storage->save($file, $newSubStoragePath);
        $dbFile = DbFile::fromFile($file);
        $dbFile->create();

        return $dbFile;
    }
}

ImagesService.php

<?php

namespace Modules\Upload\Images;

use Core\Framework\Injectable;
use DateTime;
use Modules\Upload\Files\Exceptions\FileNotSavedException;
use Modules\Upload\Files\FilesService;
use Phalcon\Encryption\Security\Random;
use Phalcon\Image\Adapter\Imagick;
use Phalcon\Image\Enum;
use Utils\Files\File;
use Utils\Text\Paths;

class ImagesService extends Injectable {
    public const IMAGES_FORMAT = 'webp';

    public const IMAGES_QUALITY = 100;

    public const MAIN_IMAGE_SIZE = 1280;

    public const THUMBNAIL_SIZES = [200, 480, 720];

    public const IMAGES_DIR_NAME = 'images';

    /**
    * @param File $file
    * @throws FileNotSavedException
    * @return Image
    */
    public function upload(File $file): Image {
        $mainImageFile = $this->buildMainImage($file);
        $filesService = new FilesService();
        $image = new Image();
        $generatedPath = $this->generatePath($mainImageFile);
        $image->setDbFile($filesService->upload(
            $mainImageFile,
            $generatedPath
        ));

        $thumbnails = [];
        $directory = pathinfo($generatedPath, PATHINFO_DIRNAME);

        foreach (self::THUMBNAIL_SIZES as $size) {
            $filename = $mainImageFile->getShortName() .
            '_' . $size .
            '.' . self::IMAGES_FORMAT;

            $path = Paths::join([$directory, $filename]);

            $thumbnails = [
                ...$thumbnails,
                (new Thumbnail())->setImage($image)
                    ->setDbFile($filesService->upload(
                        $this->buildThumbnailFile(
                            $mainImageFile,
                            $size
                        ),
                        $path
                    ))->setSize($size)
            ];
        }

        $image->setThumbnails($thumbnails);
        $image->create();

        return $image;
    }

    /**
    * @param File $file
    * @return File
    */
    private function buildMainImage(File $file): File {
        $imagick = new Imagick($file->getPath());

        if ($imagick->getWidth() > $imagick->getHeight()) {
            $imagick->resize(self::MAIN_IMAGE_SIZE, null, Enum::WIDTH);
        } else {
            $imagick->resize(null, self::MAIN_IMAGE_SIZE, Enum::HEIGHT);
        }

        $binary = $imagick->render(
            self::IMAGES_FORMAT,
            self::IMAGES_QUALITY
        );

        return File::fromBinary($binary);
    }

    /**
    * @param File $imageFile
    * @param integer $size
    * @return File
    */
    private function buildThumbnailFile(
        File $imageFile,
        int $size
    ): File {
        $imagick = new Imagick($imageFile->getPath());

        if ($imagick->getWidth() > $imagick->getHeight()) {
            $imagick->resize($size, null, Enum::WIDTH);
        } else {
            $imagick->resize(null, $size, Enum::HEIGHT);
        }

        $binary = $imagick->render(
            self::IMAGES_FORMAT,
            self::IMAGES_QUALITY
        );

        return File::fromBinary($binary);
    }

    /**
    * @return string
    */
    private function generatePath(File $file): string {
        $date = new DateTime();
        $year = $date->format('Y');
        $month = $date->format('m');
        $uuid = (new Random())->uuid();

        return self::IMAGES_DIR_NAME .
            '/' . $year .
            '/' . $month .
            '/' . $uuid .
            '.' . $file->getExtension();
    }
}

Base classes for models: BaseModel.php


<?php

namespace Core\Models;

use Core\Services\Storage;
use Phalcon\Cache\Adapter\AdapterInterface as CacheAdapterInterface;
use Phalcon\Db\Adapter\AdapterInterface as DbAdapterInterface;
use Phalcon\Mvc\Model;
use ReflectionClass;
use ReflectionProperty;
use Utils\Text\Strings;

abstract class BaseModel extends Model {
    /**
    * @var string[]
    */
    protected array $modelProperties;

    /**
    * @var integer|null
    */
    protected ?int $id = null;

    /**
    * @return integer|null
    */
    public function getId(): int|null {
        return $this->id;
    }

    /**
    * @param integer|null $id
    * @return static
    */
    public function setId($id): static {
        $this->id = $id;

        return $this;
    }

    /**
    * @return string[]
    */
    public function getModelProperties() {
        if (!isset($this->modelProperties)) {
            $this->modelProperties = $this->scanProperties();
        }

        return $this->modelProperties;
    }

    /**
    * @return boolean
    */
    public function isSaved(): bool {
        return !empty($this->id);
    }

    /**
    * @return boolean
    */
    public function isNew(): bool {
        return !$this->isSaved();
    }

    /**
    * @return void
    */
    public function initialize(): void {
        $this->useDynamicUpdate(true);

        $class = new \ReflectionClass($this);
        $className = $class->getShortName();
        $namespaceName = $class->getNamespaceName();
        $moduleName = explode("\\", $namespaceName)[1];
        $tableName = Strings::camelCaseToSnakeCase($className);

        $this->setSchema(strtolower($moduleName));
        $this->setSource($tableName);

        $this->addBehavior(new ErrorLogBehavior([
            'notSaved' => [],
            'notDeleted' => []
        ]));
    }

    /**
    * @return array
    */
    public function columnMap(): array {
        return $this->buildColumnMap();
    }

    /**
    * @return string[]
    */
    protected function scanProperties(): array {
        $properties = [];
        $class = new ReflectionClass($this);
        $reflectProperties = $class->getProperties();

        foreach ($reflectProperties as $reflectProperty) {
            $name = $reflectProperty->getName();

            if (
                $reflectProperty->isStatic()
                || $this->isFrameworkProperty($name)
                || $name === 'modelProperties'
                || !$this->isAccessableProperty($class, $reflectProperty)
            ) {
                continue;
            }

            $properties[] = $name;
        }

        return $properties;
    }

    /**
    * @return Storage
    */
    protected function getStorage(): Storage {
        return $this->container->get('storage');
    }

    /**
    * @return CacheAdapterInterface
    */
    protected function getCache(): CacheAdapterInterface {
        return $this->container->get('cache');
    }

    /**
    * @return DbAdapterInterface
    */
    protected function getDatabase(): DbAdapterInterface {
        return $this->container->get('db');
    }

    /**
    * @param string $propertyName
    * @return boolean
    */
    private function isFrameworkProperty(string $propertyName): bool {
        return in_array($propertyName, [
            'dirtyState',
            'dirtyRelated',
            'errorMessages',
            'modelsManager',
            'modelsMetaData',
            'related',
            'operationMade',
            'oldSnapshot',
            'skipped',
            'snapshot',
            'transaction',
            'uniqueKey',
            'uniqueParams',
            'uniqueTypes',
            'container',
            'belongsTo'
        ]);
    }

    /**
    * @param ReflectionProperty $property
    * @return boolean
    */
    private function isAccessableProperty(
        ReflectionClass $class,
        ReflectionProperty $property
    ): bool {
        if ($property->isPublic()) {
            return true;
        }

        $name = $property->getName();
        $getterName = Strings::getterName($name);

        return $class->hasMethod($getterName)
            && $class->getMethod($getterName)->isPublic();
    }

    /**
    * @return array
    */
    private function buildColumnMap(): array {
        $properties = $this->getModelProperties();
        $map = [];

        foreach ($properties as $property) {
            $map[Strings::camelCaseToSnakeCase($property)] = $property;
        }

        return $map;
    }
}

TraceableModel.php


<?php

namespace Core\Models;

use DateTime;
use Phalcon\Mvc\Model\Behavior\Timestampable;
use Utils\Time\Time;

abstract class TraceableModel extends BaseModel {
    /**
    * @var int|null
    */
    protected ?string $createdAt = null;

    /**
    * @var string|null
    */
    protected ?string $updatedAt = null;

    /**
    * @return DateTime|null
    */
    public function getCreatedAt(): ?DateTime {
        if (empty($this->createdAt)) {
            return null;
        }

        return new DateTime($this->createdAt);
    }

    /**
    * @param int $createdAt
    * @return static
    */
    public function setCreatedAt(string $createdAt): static {
        $this->createdAt = $createdAt;

        return $this;
    }

    /**
    * @return DateTime|null
    */
    public function getUpdatedAt(): ?DateTime {
        if (empty($this->updatedAt)) {
            return null;
        }

        return new DateTime($this->updatedAt);
    }

    /**
    * @param int $updatedAt
    * @return static
    */
    public function setUpdatedAt(string $updatedAt): static {
        // $this->updatedAt = $updatedAt->format(Time::DATETIME_FORMAT);
        $this->updatedAt = $updatedAt;

        return $this;
    }

    /**
    * @return void
    */
    public function initialize(): void {
        parent::initialize();

        $this->addBehavior(new Timestampable([
            'beforeValidationOnCreate' => [
                'field' => 'created_at',
                'format' => Time::DATETIME_FORMAT,
            ],
        ]));

        $this->addBehavior(new Timestampable([
            'beforeValidationOnUpdate' => [
                'field' => 'updated_at',
                'format' => Time::DATETIME_FORMAT,
            ],
            'beforeValidationOnCreate' => [
                'field' => 'updated_at',
                'format' => Time::DATETIME_FORMAT,
            ],
        ]));
    }
}

Models: DbFile.php

<?php

namespace Modules\Upload\Files;

use Core\Models\TraceableModel;
use Modules\Upload\Files\Exceptions\FileNotSavedException;
use Utils\Files\File;
use Utils\Serialize\HiddenFields;

class DbFile extends TraceableModel implements HiddenFields {
    /**
    * @var string
    */
    protected string $link;

    /**
    * @var string
    */
    protected string $mimeType;

    /**
    * @var integer
    */
    protected int $size;

    /**
    * @var File
    */
    protected File $file;

    /**
    * @param File $file
    * @throws FileNotSavedException
    * @return static
    */
    public static function fromFile(File $file): static {
        $dbFile = new static();
        $dbFile->file = $file;
        $dbFile->size = $file->getSize();
        $dbFile->mimeType = $file->getMime();
        $storage = $dbFile->getStorage();
        $link = $storage->getLinkFromFile($file);

        if (empty($link)) {
            throw new FileNotSavedException($file->getPath());
        }

        $dbFile->link = $link;

        return $dbFile;
    }

    public function initialize(): void {
        parent::initialize();

        $this->hasMany(
            'id',
            DbFileDependency::class,
            'db_file_id',
            [
                'alias' => 'dependencies',
                'reusable' => true
            ]
        );
    }

    /**
    * @return string
    */
    public function getLink(): string {
        return $this->link;
    }

    /**
    * @return string
    */
    public function getMimeType(): string {
        return $this->mimeType;
    }

    /**
    * @return integer
    */
    public function getSize(): int {
        return $this->size;
    }

    /**
    * @return DbFileDependency[]
    */
    public function getDependencies(): array {
        return $this->dependencies;
    }

    /**
    * @param DbFileDependency[] $dependencies
    * @return static
    */
    public function setDependencies(array $dependencies): static {
        $this->dependencies = $dependencies;

        return $this;
    }

    /**
    * @return File
    */
    public function getFile(): File {
        if (!isset($this->file)) {
            $this->file = $this->getStorage()->openLink($this->link);
        }

        return $this->file;
    }

    /**
    * @return string[]
    */
    public function getHiddenFields(): array {
        return [
            'file',
            'dependencies'
        ];
    }
}

Image.php


<?php

namespace Modules\Upload\Images;

use Core\Models\BaseModel;
use Modules\Upload\Files\DbFile;

class Image extends BaseModel {
    public const IMAGES_DIR_NAME = 'images';

    /**
    * @var boolean
    */
    protected bool $isVector;

    /**
    * @var integer
    */
    protected int $dbFileId;

    public function initialize(): void {
        parent::initialize();

        $this->belongsTo(
            'db_file_id',
            DbFile::class,
            'id',
            [
                'alias' => 'dbFile',
                'foreignKey' => true,
                'reusable' => true
            ]
        );

        $this->hasMany(
            'id',
            Thumbnail::class,
            'image_id',
            [
                'alias' => 'Thumbnails',
                'reusable' => true
            ]
        );
    }

    /**
    * @return boolean
    */
    public function getIsVector() {
        return $this->isVector;
    }

    /**
    * @param boolean $isVector
    * @return static
    */
    public function setIsVector(bool $isVector): static {
        $this->isVector = $isVector;

        return $this;
    }

    /**
    * @return integer
    */
    public function getDbFileId(): int {
        return $this->dbFileId;
    }

    /**
    * @param integer $dbFileId
    * @return static
    */
    public function setDbFileId(int $dbFileId): static {
        $this->dbFileId = $dbFileId;

        return $this;
    }

    /**
    * @return DbFile
    */
    public function getDbFile(): DbFile {
        return $this->dbFile;
    }

    /**
    * @param DbFile $dbFile
    * @return static
    */
    public function setDbFile(DbFile $dbFile): static {
        $this->dbFile = $dbFile;

        if ($dbFile->getMimeType() === 'image/svg+xml') {
            $this->isVector = true;
        } else {
            $this->isVector = false;
        }

        return $this;
    }

    /**
    * @return Thumbnail[]
    */
    public function getThumbnails(): array {
        return $this->thumbnails;
    }

    /**
    * @param Thumbnail[] $thumbnails
    * @return static
    */
    public function setThumbnails(iterable $thumbnails): static {
        $this->thumbnails = [...$thumbnails];

        return $this;
    }

    /**
    * @param Thumbnail $thumbnail
    * @return static
    */
    public function addThumbnail(Thumbnail $thumbnail): static {
        $this->thumbnails = [
            ...$this->thumbnails,
            $thumbnail
        ];

        return $this;
    }
}

Errors and logs:

SQL logs

[2023-01-11T14:43:50+02:00][DEBUG] NULL
[2023-01-11T14:43:50+02:00][DEBUG] SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END FROM information_schema.tables WHERE table_schema = 'security' AND table_name='user'
    [2023-01-11T14:43:50+02:00][DEBUG] SELECT DISTINCT c.column_name AS Field, c.data_type AS Type, c.character_maximum_length AS Size, c.numeric_precision AS NumericSize, c.numeric_scale AS NumericScale, c.is_nullable AS Null, CASE WHEN pkc.column_name NOTNULL THEN 'PRI' ELSE '' END AS Key, CASE WHEN c.data_type LIKE '%int%' AND c.column_default LIKE '%nextval%' THEN 'auto_increment' ELSE '' END AS Extra, c.ordinal_position AS Position, c.column_default, des.description FROM information_schema.columns c LEFT JOIN ( SELECT kcu.column_name, kcu.table_name, kcu.table_schema FROM information_schema.table_constraints tc INNER JOIN information_schema.key_column_usage kcu on (kcu.constraint_name = tc.constraint_name and kcu.table_name=tc.table_name and kcu.table_schema=tc.table_schema) WHERE tc.constraint_type='PRIMARY KEY') pkc ON (c.column_name=pkc.column_name AND c.table_schema = pkc.table_schema AND c.table_name=pkc.table_name) LEFT JOIN ( SELECT objsubid, description, relname, nspname FROM pg_description JOIN pg_class ON pg_description.objoid = pg_class.oid JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid ) des ON ( des.objsubid = C.ordinal_position AND C.table_schema = des.nspname AND C.TABLE_NAME = des.relname ) WHERE c.table_schema='security' AND c.table_name='user' ORDER BY c.ordinal_position

[2023-01-11T14:43:50+02:00][DEBUG] SELECT "user"."id", "user"."firstname", "user"."lastname", "user"."email", "user"."phone", "user"."role", "user"."password_hash", "user"."created_at", "user"."updated_at", "user"."last_signed_in" FROM "security"."user" LIMIT 1

[2023-01-11T14:43:55+02:00][DEBUG] NULL

[2023-01-11T14:43:55+02:00][DEBUG] SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END FROM information_schema.tables WHERE table_schema = 'upload' AND table_name='db_file'

[2023-01-11T14:43:55+02:00][DEBUG] SELECT DISTINCT c.column_name AS Field, c.data_type AS Type, c.character_maximum_length AS Size, c.numeric_precision AS NumericSize, c.numeric_scale AS NumericScale, c.is_nullable AS Null, CASE WHEN pkc.column_name NOTNULL THEN 'PRI' ELSE '' END AS Key, CASE WHEN c.data_type LIKE '%int%' AND c.column_default LIKE '%nextval%' THEN 'auto_increment' ELSE '' END AS Extra, c.ordinal_position AS Position, c.column_default, des.description FROM information_schema.columns c LEFT JOIN ( SELECT kcu.column_name, kcu.table_name, kcu.table_schema FROM information_schema.table_constraints tc INNER JOIN information_schema.key_column_usage kcu on (kcu.constraint_name = tc.constraint_name and kcu.table_name=tc.table_name and kcu.table_schema=tc.table_schema) WHERE tc.constraint_type='PRIMARY KEY') pkc ON (c.column_name=pkc.column_name AND c.table_schema = pkc.table_schema AND c.table_name=pkc.table_name) LEFT JOIN ( SELECT objsubid, description, relname, nspname FROM pg_description JOIN pg_class ON pg_description.objoid = pg_class.oid JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid ) des ON ( des.objsubid = C.ordinal_position AND C.table_schema = des.nspname AND C.TABLE_NAME = des.relname ) WHERE c.table_schema='upload' AND c.table_name='db_file' ORDER BY c.ordinal_position

[2023-01-11T14:43:55+02:00][DEBUG] INSERT INTO "upload"."db_file" ("link", "mime_type", "size", "created_at", "updated_at", "id") VALUES ('/storage/images/2023/01/68909d66-4ea0-4e88-b268-a5b61a424ecc.webp', 'image/webp', 136710, '2023-01-11 14:43:55.000000+02:00', '2023-01-11 14:43:55.000000+02:00', DEFAULT)

[2023-01-11T14:43:55+02:00][DEBUG] INSERT INTO "upload"."db_file" ("link", "mime_type", "size", "created_at", "updated_at", "id") VALUES ('/storage/images/2023/01/68909d66-4ea0-4e88-b268-a5b61a424ecc_200.webp', 'image/webp', 6924, '2023-01-11 14:43:55.000000+02:00', '2023-01-11 14:43:55.000000+02:00', DEFAULT)

[2023-01-11T14:43:55+02:00][DEBUG] INSERT INTO "upload"."db_file" ("link", "mime_type", "size", "created_at", "updated_at", "id") VALUES ('/storage/images/2023/01/68909d66-4ea0-4e88-b268-a5b61a424ecc_480.webp', 'image/webp', 23806, '2023-01-11 14:43:55.000000+02:00', '2023-01-11 14:43:55.000000+02:00', DEFAULT)

[2023-01-11T14:43:55+02:00][DEBUG] INSERT INTO "upload"."db_file" ("link", "mime_type", "size", "created_at", "updated_at", "id") VALUES ('/storage/images/2023/01/68909d66-4ea0-4e88-b268-a5b61a424ecc_720.webp', 'image/webp', 35346, '2023-01-11 14:43:55.000000+02:00', '2023-01-11 14:43:55.000000+02:00', DEFAULT)

[2023-01-11T14:43:55+02:00][DEBUG] SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END FROM information_schema.tables WHERE table_schema = 'upload' AND table_name='image'

[2023-01-11T14:43:55+02:00][DEBUG] SELECT DISTINCT c.column_name AS Field, c.data_type AS Type, c.character_maximum_length AS Size, c.numeric_precision AS NumericSize, c.numeric_scale AS NumericScale, c.is_nullable AS Null, CASE WHEN pkc.column_name NOTNULL THEN 'PRI' ELSE '' END AS Key, CASE WHEN c.data_type LIKE '%int%' AND c.column_default LIKE '%nextval%' THEN 'auto_increment' ELSE '' END AS Extra, c.ordinal_position AS Position, c.column_default, des.description FROM information_schema.columns c LEFT JOIN ( SELECT kcu.column_name, kcu.table_name, kcu.table_schema FROM information_schema.table_constraints tc INNER JOIN information_schema.key_column_usage kcu on (kcu.constraint_name = tc.constraint_name and kcu.table_name=tc.table_name and kcu.table_schema=tc.table_schema) WHERE tc.constraint_type='PRIMARY KEY') pkc ON (c.column_name=pkc.column_name AND c.table_schema = pkc.table_schema AND c.table_name=pkc.table_name) LEFT JOIN ( SELECT objsubid, description, relname, nspname FROM pg_description JOIN pg_class ON pg_description.objoid = pg_class.oid JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid ) des ON ( des.objsubid = C.ordinal_position AND C.table_schema = des.nspname AND C.TABLE_NAME = des.relname ) WHERE c.table_schema='upload' AND c.table_name='image' ORDER BY c.ordinal_position

[2023-01-11T14:43:55+02:00][DEBUG] NULL

[2023-01-11T14:43:55+02:00][DEBUG] SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END FROM information_schema.tables WHERE table_schema = 'upload' AND table_name='db_file'

    [2023-01-11T14:43:55+02:00][DEBUG] SELECT DISTINCT c.column_name AS Field, c.data_type AS Type, c.character_maximum_length AS Size, c.numeric_precision AS NumericSize, c.numeric_scale AS NumericScale, c.is_nullable AS Null, CASE WHEN pkc.column_name NOTNULL THEN 'PRI' ELSE '' END AS Key, CASE WHEN c.data_type LIKE '%int%' AND c.column_default LIKE '%nextval%' THEN 'auto_increment' ELSE '' END AS Extra, c.ordinal_position AS Position, c.column_default, des.description FROM information_schema.columns c LEFT JOIN ( SELECT kcu.column_name, kcu.table_name, kcu.table_schema FROM information_schema.table_constraints tc INNER JOIN information_schema.key_column_usage kcu on (kcu.constraint_name = tc.constraint_name and kcu.table_name=tc.table_name and kcu.table_schema=tc.table_schema) WHERE tc.constraint_type='PRIMARY KEY') pkc ON (c.column_name=pkc.column_name AND c.table_schema = pkc.table_schema AND c.table_name=pkc.table_name) LEFT JOIN ( SELECT objsubid, description, relname, nspname FROM pg_description JOIN pg_class ON pg_description.objoid = pg_class.oid JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid ) des ON ( des.objsubid = C.ordinal_position AND C.table_schema = des.nspname AND C.TABLE_NAME = des.relname ) WHERE c.table_schema='upload' AND c.table_name='db_file' ORDER BY c.ordinal_position

Error log

[2023-01-11T14:43:55+02:00][ERROR] Failed to save model 'Modules\Upload\Images\Image': array (
    0 => 
    Phalcon\Messages\Message::__set_state(array(
        'code' => 0,
        'field' => 'db_file_id',
        'message' => 'Value of field "db_file_id" does not exist on referenced table',
        'type' => 'ConstraintViolation',
        'metaData' => 
        array (
        ),
    )),
    )

Checked posts on phalcon forum with similar issue, but I don't have mistakes that was in their case.

Checked resources: #1 #2

I found where was a problem. In relationships declarations I specified column names instead of property names like $this->belongsTo('db_file_id... instead of $this->belongsTo('dbFileId... . After I changed it to property names, all works.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM