cycle / annotated Goto Github PK
View Code? Open in Web Editor NEWSchema generation using annotated entities and mappers
License: MIT License
Schema generation using annotated entities and mappers
License: MIT License
When adding an index on a child table in JTI, it tries to add to the parent table.
In the branch, I created the test.
For example, I created Entity Person:
/**
* @Entity
*/
#[Entity]
class Person
{
/** @Column(type="primary", name="id") */
#[Column(type: 'primary', name: 'id')]
protected int $foo_id;
/** @Column(type="string") */
#[Column(type: 'string')]
public string $type;
}
After that, I created the Buyer entity and extended it from Person, added an index to the index_id column. But the index is trying to add to the people table, which is related to the Person entity.
/**
* @Entity
* @Index(columns={"index_id"}, unique=true)
* @InheritanceJoinedTable
*/
#[Entity]
#[Index(columns: ['index_id'], unique: true)]
#[InheritanceJoinedTable]
class Buyer extends Person
{
/** @Column(type="integer") */
#[Column(type: 'integer')]
private int $index_id;
}
Problem:
When using inheritance and mark parent and child classes as entities using attributes, \Cycle\Schema\Registry
generator does not add children entities to Schema Registry.
How to reproduce:
You can reproduce it if you remove docblock comment for your annotation in tests. You can see an example here
https://github.com/makedo/annotated/tree/fail-test
I change 1 fixture by fully removing docblocks and keep only AttributeReader in tests
Proposed solution:
Remove check for docblock In Cycle\Annotated\Utils\EntityUtils::findParent
annotated v3.1.0, orm v2.1.1, PHP 8.1
No response
Generated SQL:
SELECT "serviceAccount"."key" AS "c0", "serviceAccount"."name" AS "c1", "serviceAccount"."id" AS "c2", "serviceAccount"."created_at" AS "c3", "serviceAccount"."updated_at" AS "c4",
"serviceAccount"."deleted_at" AS "c5", "l_serviceAccount_configuration"."timezone" AS "c6", "l_serviceAccount_configuration"."id" AS "c7",
"l_serviceAccount_configuration"."created_at" AS "c8", "l_serviceAccount_configuration"."updated_at" AS "c9", "l_serviceAccount_configuration"."deleted_at" AS "c10",
"l_serviceAccount_configuration"."configuration_id" AS "c11", "l_serviceAccount_configuration"."{relationName}_role" AS "c12", "l_serviceAccount_configuration"."configuration_role"
AS "c13"
FROM "service_accounts" AS "serviceAccount"
LEFT JOIN "configurations" AS "l_serviceAccount_configuration"
ON "l_serviceAccount_configuration"."configuration_id" = "serviceAccount"."id" AND "l_serviceAccount_configuration"."deleted_at" IS NULL AND "l_serviceAccount_configuration"."configuration_role" = 'serviceAccount'
WHERE "serviceAccount"."key" = 'system' AND "serviceAccount"."deleted_at" IS NULL
LIMIT 1
Sample code to reproduce:
#[Cycle\Entity]
class ServiceAccount
{
#[Cycle\Relation\Morphed\MorphedHasOne(
target: Configuration::class,
// morphKey: 'configuration_role', // FIX
load: 'eager'
)]
protected ?Configuration $configuration = null;
}
#[Cycle\Entity]
class Configuration
{
// ...
}
{relationName}
– is a template variable which not replaced before query execution.
P.S.
8.1.1
2.0
including:
Hi,
I would like to extend the functionality to support virtual generated columns.
In Doctrine it is possible using this explicit annotation:
#[ORM\Column(type: 'int', columnDefinition: 'INT GENERATED ALWAYS AS (IF(xyz IS TRUE, 100, 200)) VIRTUAL', generated: 'ALWAYS')]
private int $someNumber;
Possibly the freshly added GeneratedColumn
attribute might be used to support the functionality?
I am open to bounty this feature.
When I generate my schema using annotations I have noticed that it will set an integer auto-increment primary key with name $id to an array.
Entity PK definition:
#[Column(type: "primary")]
private int $id;
After persisting the schema to a json file:
"7": [
"id"
],
It looks like Cycle\Schema\Compiler is the culprit. Line 90 is Schema::PRIMARY_KEY => $entity->getPrimaryFields()->getNames(),
and this always returns an array.
The IMPACT of this (at least the one I've seen), is seen in Cycle\ORM\Select::count(). If the $column parameter isn't set, it looks at the primary key definition instead as follows:
if ($column === null) {
// @tuneyourserver solves the issue with counting on queries with joins.
$pk = $this->loader->getPK();
$column = \is_array($pk)
? '*'
: \sprintf('DISTINCT(%s)', $pk);
}
return (int) $this->__call('count', [$column]);
because it's always an array, it always puts in * instead of the DISTINCT() call. In a left join it will then show an incorrect count. It'll be showing the number of results in the SQL instead of the number of results in the SQL for the left/primary table.
I've confirmed this by changing the schema to not be an array, and then the count works accurately. I've also tried select->count('DISTINCT(table.id)'); and this works too (as it emulates what would happen if this was working correctly).
"cycle/orm": "^2.2",
"cycle/annotated": "^3.2",
"cycle/schema-builder": "^2.1",
"cycle/entity-behavior": "^1.1",
php 8.0.16
No response
Need to
Hi,
I would love to add a check constraint
to my table using attributes.
I imagine the functionality as following, but it is just a draft.
#[Entity]
#[Index(columns: ['a'], unique: true)]
#[Check(expression: 'a > b')]
class EntityDef
{
#[Column(type: 'integer')]
private int $a;
#[Column(type: 'integer')]
private int $b;
}
I am open to bounty this feature.
Hi,
I am trying to integrate CycleORM into a Symfony application, in order to replace Doctrine, and I encountered a strange behavior with relations.
I will show you the example first.
I trimmed all the other tables and managed to reproduce on following 4 entities:
#[Entity(database: 'main')]
class Question
{
#[Column(type: 'primary')]
private int $id;
#[BelongsTo(target: QuestionTextual::class, nullable: true)]
private ?QuestionTextual $questionTextual = null;
#[BelongsTo(target: QuestionChoice::class, nullable: true)]
private ?QuestionChoice $questionChoice = null;
#[BelongsTo(target: QuestionMultichoice::class, nullable: true)]
private ?QuestionMultichoice $questionMultichoice = null;
}
#[Entity(database: 'main')]
class QuestionChoice
{
#[Column(type: 'primary')]
private readonly int $id;
#[HasOne(target: Question::class)]
private Question $question;
}
#[Entity(database: 'main')]
class QuestionTextual
{
#[Column(type: 'primary')]
private readonly int $id;
#[HasOne(target: Question::class)]
private Question $question;
}
#[Entity(database: 'main')]
class QuestionMultichoice
{
#[Column(type: 'primary')]
private readonly int $id;
#[HasOne(target: Question::class)]
private Question $question;
}
Those entities result in following database schema:
create table question_choices
(
id int auto_increment
primary key
);
create table question_multichoices
(
id int auto_increment
primary key
);
create table question_textuals
(
id int auto_increment
primary key
);
create table questions
(
id int auto_increment
primary key,
questionChoice_id int not null,
questionTextual_id int null,
questionMultichoice_id int null,
constraint questions_foreign_questionchoice_id_66745e0c2f565
foreign key (questionChoice_id) references question_choices (id)
on update cascade on delete cascade,
constraint questions_foreign_questionmultichoice_id_66745e0c2f6fc
foreign key (questionMultichoice_id) references question_multichoices (id)
on update cascade on delete cascade,
constraint questions_foreign_questiontextual_id_66745e0c2f607
foreign key (questionTextual_id) references question_textuals (id)
on update cascade on delete cascade
);
create index questions_index_questionchoice_id_66745e0c2f53b
on questions (questionChoice_id);
create index questions_index_questionmultichoice_id_66745e0c2f6e5
on questions (questionMultichoice_id);
create index questions_index_questiontextual_id_66745e0c2f5ef
on questions (questionTextual_id);
The strange thing this issue is about is the nullability of questionChoice_id
column. For some reason the column is declared as NOT NULL, while others are nullable - and all the columns have identical attribute declaration.
In this usecase the question has 3 possible types and the tables include some additional settings. In the base table the relation is nullable, because only one of the 3 columns is filled. However in the extending tables the column is not null, because it must have a base table paired to it.
There might be some sort of misunderstanding of the CycleORM relations on my side, but it seems strange to me that one column has different nullability than the others. When the HasOne
attribute is removed from the entitiy, the nullability is corrected.
I cannot tell as I am a new user of Cycle ORM.
Use the default setup and sync a database with entitiy declaration above.
The column should be nullable.
No response
MySQL
No response
It would be cool if we could use PHP 8 Attributes for defining the schema
Shouldn't annotation though
be changed to through
(i.e. "using", "by means of")?
I have an idea, listen to me!!
Add the ability to map column to property without declaring it in migration
Hi, thank you for this library.
It looks like this package silently depends on doctrine/inflector ^1.4
API, here for example -
annotated/src/Configurator.php
Line 40 in 7dd29c0
This dependency satisfied by cycle/orm
, which is requires doctrine/inflector ^1.3, and usually install 1.4
But, It may cause problems (like in my case), when application already has 1.3 version of inflector
.
Maybe doctrine/inflector ^1.4
dependency should be added to this package?
Hello ! Thanks for your product !
I'm create embeddable class for my base entity.
#[Embeddable(columnPrefix: 'property_')]
final class ProductProperty
{
#[Column(type: 'decimal', nullable: false, default: 0, precision: 2, scale: 10)]
private int $width = 0;
// and others columns
}
When i generate migration file with the command:
php app.php cycle:migrate
I get such a file that does not include params precision and scale params:
// Parent entity
final class Product
{
#[Column(type: 'decimal', nullable: true, precision: 2, scale: 2)]
private int $minStock = 0;
#[Embedded(target: 'ProductProperty')]
private ProductProperty $property;
public function __construct(){
$this->property = new ProductProperty();
}
}
// Generated migration for 'Product'
class OrmDefault503ad0138adb712888e44c220d4cf5aa extends Migration
{
public function up(): void
{
$this->table('product')
->addColumn('min_stock', 'decimal', ['nullable' => true, 'defaultValue' => null, 'precision' => 2, 'scale' => 2]); /// This property in base entity
->addColumn('property_width', 'decimal', ['nullable' => false, 'defaultValue' => 0, 'precision' => 0, 'scale' => 0]) // And this in Embedded entity
}
}
I will be grateful for your help!
annotated v4.1.0, orm v2.8.1, PHP 8.3
No response
I'm reading through the docs and I see
/** @ManyToMany(target = "Tag", though = "UserTag") */
protected $tags;
At first glance, I read this as "entity has many Tag
through UserTag
" where UserTag
is the join class somewhat. Thought to fix that and realized 👇🏾 this is how it's defined.
https://github.com/cycle/annotated/blob/master/src/Annotation/Relation/ManyToMany.php#L59
Could this be a case of a typo carried over because of IDE smarts? If this isn't the case, what's the thought process? I see this commit introduces "though"
, but the commit message doesn't really say why.
I know this is 2 years old, so it's a long shot; but I had to ask
For example there is namespace Cycle\Annotated\Annotation;
located in resources/stubs
and also in src/Annotation
. This can't be right, right? It is messing with psalm and other alike tools. Is there any solution for that?
Error generated by psalm looks like this:
ERROR: InvalidNamedArgument - app/Entity/Trait/CreatedAtTrait.php:12:17 - Parameter $type does not exist on function Cycle\Annotated\Annotation\Column::__construct (see https://psalm.dev/238)
#[Column(type: 'datetime')]
It is probably using the first occurence in alphabet for that namespace?
For thw 4.0 release we need to fix JTI/STI attributes harvesting.
That fix won't be applied for 4.0 but tests are useful #65
It would be nice to be able to define charsets for columns like this:
#[Entity]
class User
{
#[Column(type: 'primary')]
private int $id;
#[Column(type: 'string', name: 'username', charset: 'latin1', collation: 'latin1_bin')]
private string $email;
}
Originally posted by @ardabeyazoglu in cycle/orm#370
Add an ability to define FK using attributes
Do u know that
/** @HasOne(target=Profile::class, load="eager") */
don't work without
use Cycle\Annotated\Annotation\Relation\HasOne
and so on will all entities from \Relation
and this is not obviously - people need to study your code.
Refresh readme, add link to documentation
I think we can move doctrine/annotations
into require-dev
section.
Hi,
In a scenario where some sort of BaseEntity
is used to contain Column
s appearing across multiple Entity
s, those columns are not registered into the Entity
s extending this base class.
abstract class EmployedPerson
{
#[Column(type: 'integer')]
protected int $id;
}
#[Entity]
class Developer extends EmployedPerson {
#[Column(type: 'integer')]
protected int $salary;
}
In the example above, the id
column is not added to the Developer
entity.
This is probably caused by Configurator:115
:
$field->setEntityClass($property->getDeclaringClass()->getName());
where the field is set to entity by a declaring class, but this class is not an entity.
This should work also in combination with JTI, where a following hierarchy could be used:
#[Entity]
class Person {
#[Column(type: 'primary')]
protected int $id;
}
abstract class EmployedPerson extends Person
{
#[Column(type: 'integer')]
protected int $salary;
}
#[Entity]
#[JoinedTable]
class Developer extends EmployedPerson {
#[Column(type: 'integer')]
protected int $foo;
}
In the example above, the Developer table should contain columns id
, salary
and foo
, but currently the salary
is ignored.
No response
Defining a prefix in the Embedded relation will allow the following cases:
[ErrorException]
The "Doctrine\Common\Inflector\Inflector::tableize" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.
in /app/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php:80
Exception Trace:
Spiral\Boot\ExceptionHandler::handleError() at /app/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php:80
Spiral\Boot\ExceptionHandler::handleError() at n/a:n/a
trigger_error() at /app/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php:80
Doctrine\Common\Inflector\Inflector::tableize() at /app/vendor/cycle/annotated/src/Entities.php:191
Cycle\Annotated\Entities->tableName() at /app/vendor/cycle/annotated/src/Entities.php:106
Cycle\Annotated\Entities->run() at /app/vendor/cycle/schema-builder/src/Compiler.php:63
Cycle\Schema\Compiler->compile() at /app/vendor/spiral/framework/src/Bootloader/Cycle/SchemaBootloader.php:113
Spiral\Bootloader\Cycle\SchemaBootloader->schema() at n/a:n/a
ReflectionMethod->invokeArgs() at /app/vendor/spiral/core/src/Container.php:506
Spiral\Core\Container->evaluateBinding() at /app/vendor/spiral/core/src/Container.php:166
Spiral\Core\Container->make() at /app/vendor/spiral/core/src/Container.php:133
Spiral\Core\Container->get() at /app/vendor/spiral/prototype/src/Bootloader/PrototypeBootloader.php:170
Spiral\Prototype\Bootloader\PrototypeBootloader->initCycle() at /app/vendor/spiral/prototype/src/Bootloader/PrototypeBootloader.php:112
Spiral\Prototype\Bootloader\PrototypeBootloader->boot() at n/a:n/a
ReflectionMethod->invokeArgs() at /app/vendor/spiral/boot/src/BootloadManager.php:124
Spiral\Boot\BootloadManager->initBootloader() at /app/vendor/spiral/boot/src/BootloadManager.php:98
Spiral\Boot\BootloadManager->boot() at /app/vendor/spiral/boot/src/BootloadManager.php:66
Spiral\Boot\BootloadManager->Spiral\Boot\{closure}() at /app/vendor/spiral/core/src/Container.php:282
Spiral\Core\Container->runScope() at /app/vendor/spiral/boot/src/BootloadManager.php:67
Spiral\Boot\BootloadManager->bootload() at /app/vendor/spiral/framework/src/Framework/Kernel.php:35
Spiral\Framework\Kernel->bootstrap() at /app/vendor/spiral/boot/src/AbstractKernel.php:142
Spiral\Boot\AbstractKernel::Spiral\Boot\{closure}() at /app/vendor/spiral/core/src/ContainerScope.php:50
Spiral\Core\ContainerScope::runScope() at /app/vendor/spiral/core/src/Container.php:279
Spiral\Core\Container->runScope() at /app/vendor/spiral/boot/src/AbstractKernel.php:143
Spiral\Boot\AbstractKernel::init() at /app/app.php:18
Если использовать псевдоним названия класса, а не название аннотации, то парсер не найдёт аннотацию, на сколько я понял такое поведение из-за особенностей реализации библиотеки https://github.com/spiral/annotations.
В отличии от аннотаций доктрины, где название аннотации == название класса и есть поддержка псевдонимов.
Т.е. такой вариант использования аннотаций не сработает
namespace App\Entity;
use Cycle\Annotated\Annotation\Entity as CycleEntity;
/**
* @CycleEntity
*/
class Order
{
Стоит ли внедрять поддержку псевдонимов или есть причины именно такой реализации, где аннотация !== название класса?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.