字段"..."的值在引用的表中不存在
尝试使用phalcon模型关系来保存相关模型,但出现错误:
引用表中不存在字段"db_file_id"的值
保存和处理服务:
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();
}
}
模型的基类: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,
],
]));
}
}
文件类型: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;
}
}
错误和日志:
SQL日志
[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
错误日志
[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 (
),
)),
)
1条答案
按热度按时间pgvzfuti1#
我发现了问题所在,在关系声明中,我指定了列名而不是属性名,例如$this-〉belongsTo('db_file_id...而不是$this-〉belongsTo(' dbFileId....在我将其更改为属性名之后,所有工作正常。