Giter Site home page Giter Site logo

orm's Introduction

Cycle ORM

Latest Stable Version Build Status Scrutinizer Code Quality Codecov

Cycle ORM

Cycle is PHP DataMapper, ORM and Data Modelling engine designed to safely work in classic and demonized PHP applications (like RoadRunner). The ORM provides flexible configuration options to model datasets, powerful query builder and supports dynamic mapping schema. The engine can work with plain PHP objects, support annotation declarations, and proxies via extensions.

Website and Documentation | Comparison with Eloquent and Doctrine

Features

  • ORM with has-one, has-many, many-through-many and polymorphic relations
  • Plain Old PHP objects, AR, Custom objects or same entity type for multiple repositories
  • eager and lazy loading, query builder with multiple fetch strategies
  • embedded entities, lazy/eager loaded embedded partials
  • runtime configuration with/without code-generation
  • column-to-field mapping, single table inheritance, value objects support
  • hackable: persist strategies, mappers, relations, transactions
  • works with directed graphs and cyclic graphs using command chains
  • designed to work in long-running applications: immutable service core, disposable UoW
  • supports MySQL, MariaDB, PostgresSQL, SQLServer, SQLite
  • schema scaffolding, introspection, migrations and debugging
  • supports global query scopes, UUIDs as PK, soft deletes, auto timestamps and macros
  • custom column types, FKs to non-primary columns
  • use with or without annotations, proxy classes, and auto-migrations
  • compatible with Doctrine Collections, Illuminate Collections and custom collections
  • compatible with Doctrine Annotations, PHP8 attributes

Extensions

Component Current Status
cycle/schema-builder Latest Stable Version Build Status Scrutinizer Code Quality Codecov
cycle/schema-renderer Latest Stable Version Build Status Scrutinizer Code Quality Codecov
cycle/annotated Latest Stable Version Build Status Scrutinizer Code Quality Codecov
cycle/migrations Latest Stable Version Build Status Scrutinizer Code Quality Codecov
cycle/entity-behavior Latest Stable Version Build Status Scrutinizer Code Quality Codecov
cycle/entity-behavior-uuid Latest Stable Version Build Status Scrutinizer Code Quality Codecov
cycle/database Latest Stable Version Build Status Scrutinizer Code Quality Codecov
cycle/schema-migrations-generator Latest Stable Version Build Status Scrutinizer Code Quality Codecov
cycle/orm-promise-mapper Latest Stable Version Build Status Scrutinizer Code Quality Codecov

Example:

// load all active users and pre-load their paid orders sorted from newest to olders
// the pre-load will be complete using LEFT JOIN
$users = $orm->getRepository(User::class)
    ->select()
    ->where('active', true)
    ->load('orders', [
        'method' => Select::SINGLE_QUERY,
        'load'   => function($q) {
            $q->where('paid', true)->orderBy('timeCreated', 'DESC');
        }
    ])
    ->fetchAll();

$em = new EntityManager($orm);

foreach($users as $user) {
    $em->persist($user);
}

$em->run();

License:

The MIT License (MIT). Please see LICENSE for more information. Maintained by Spiral Scout.

orm's People

Contributors

albertborsos avatar alexndr-novikov avatar andrew-demb avatar belaryc avatar butschster avatar chadsikorra avatar drupol avatar eugentis avatar evgenybarinov avatar gam6itko avatar hustlahusky avatar lotyp avatar meekstellar avatar mishfish avatar mrakolice avatar msmakouz avatar pine3ree avatar rauanmayemir avatar roxblnfk avatar rustamwin avatar serafimarts avatar stylecibot avatar thenotsoft avatar vjik avatar vladgorenkin avatar vvval avatar wolfy-j avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

orm's Issues

Many-to-many: integrity constraint violation

If I add fields that are generated by relationships to the crosstab Entity class:

/** @Entity */
class PostTag
{
    /** @Column(type="primary") */
    private ?int $id = null;
    private ?int $post_id = null;
    private ?int $tag_id = null;
}

And when adding fixtures i will bind both entities:

$post->addTag($tag);
$tag->addPost($post);

Then i get an error:
image

If i remove $tag->addPost($post); or fields $post_id and $tag_id then will be good

Relations in entities:
Entity Tag https://github.com/roxblnfk/yii-demo/blob/bug/many-to-many/src/Blog/Entity/Post.php#L90
Entity Post https://github.com/roxblnfk/yii-demo/blob/bug/many-to-many/src/Blog/Entity/Tag.php#L45

php 7.4.1

How to reproduce

  1. git clone https://github.com/roxblnfk/yii-demo.git and git checkout bug/many-to-many
  2. Configure config/params.php (you can skip this step)
  3. Run composer install
  4. Run vendor/bin/yii serve or start your web-server
  5. Go to index page.
    On this step the Cycle ORM will create tables, indexes and relations automatically in the configured DB.
    If you want to disable this behavior then comment out line with Generator\SyncTables::class in the config/params.php file. In this case you should create migrations to sync changes of entities with DB
  6. Run vendor/bin/yii fixture/add 20 to create random data

RefersTo innerKey naming error

Let's say we have 2 entities, which will look like the next ones:

  • Entity A:
<?php

declare(strict_types=1);

namespace App;

use Cycle\Annotated\Annotation as Cycle;

/** @Cycle\Entity() */
class A
{
    /** @Cycle\Column(type="int", name="a_key") */
    public $aKey;
}
  • and entity B:
<?php

declare(strict_types=1);

namespace App;

use Cycle\Annotated\Annotation as Cycle;

/** @Cycle\Entity() */
class B
{
    /** @Cycle\Column(type="int", name="b_key") */
    public $bKey;
}

Entity A refers to entity B using $aKey and $bKey inner/outer keys. Let's add this relation to entity A using property names:

// somewhere inside of entity A
/**
 * @Cycle\Relation\RefersTo(target="B", innerKey="aKey", outerKey="bKey")
 * @var B
 */
public $b;

This code will fail on insert because (at least in PostgreSQL) the generated SQL is trying to add both a_key and aKey columns:

INSERT INTO "a" (..., "a_key", ..., "aKey") VALUES (..., NULL, ..., <aKey value>)

a_key appears to be null, and the aKey, which is unknown to the db scheme contains the required value.

As a fix right now, the referTo relation should be declared as below:

// somewhere inside of entity A
/**
 * @Cycle\Relation\RefersTo(target="B", innerKey="a_key", outerKey="bKey")
 * @var B
 */
public $b;

The SQL will be correct now:

INSERT INTO "a_named_b_props" (..., "a_key", ...) VALUES (..., <aKey value>, ...)

Meanwhile, using a_key and b_key as inner/outer keys:

// somewhere inside of entity A
/**
 * @Cycle\Relation\RefersTo(target="B", innerKey="a_key", outerKey="b_key")
 * @var B
 */
public $b;

leads to a Cycle\Schema\Exception\SchemaException

Unable to compute relation `a`.'b'

Unable to resolve relation target for single table entities

run cycle:sync and got:

[Cycle\Schema\Exception\RelationException]
Unable to resolve last.info relation target (not found or invalid)

Entities example:

/** @Entity */
class Post 
{
    /** @Column(type="primary") */
    public $id;
}

/** @Entity */
class Article extends Post
{
    /** @Column(type="string") */
    public $articleTitle;
}

/** @Entity */
class Last
{
    /** @HasOne(target="Article") */
    public $info;
}

Typed property must not be accessed before initialization

Try to add php 7.4 property typing to any of the Entity examples from docs and persist it with db, you will get that exception.

For the reference see also doctrine issue and corresponding PR. As far as I see the issue is partially resolved, see this one.

Not sure whether the issue in Doctrine was only for id (i.e. primary keys), but here we have the issue for any typed field in Entity.

performance problem

When creating an entity, too many requests are generated as a result the speed of work is slow.

entity has only one dependency hasOne

$user = new User('[email protected]');

$tr = new Transaction($orm);
$tr->persist($user);
$tr->run();
[2020-01-28T17:15:29.240219+02:00] database.INFO: Begin transaction [] []
[2020-01-28T17:15:29.244725+02:00] database.INFO: SELECT nspname FROM pg_namespace WHERE nspname !~ '^pg_.*' AND nspname != 'information_schema'; {"elapsed":0.0007541179656982422} []
[2020-01-28T17:15:29.246982+02:00] database.INFO: SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema = 'f_data'           AND table_type = 'BASE TABLE' AND table_name = 'user' {"elapsed":0.002146005630493164} []
[2020-01-28T17:15:29.248863+02:00] database.INFO: SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema = 'f_data'           AND table_type = 'BASE TABLE' AND table_name = 'user' {"elapsed":0.0007250308990478516} []
[2020-01-28T17:15:29.249161+02:00] database.INFO: SELECT oid FROM pg_class WHERE relname = 'user' {"elapsed":0.0002269744873046875} []
[2020-01-28T17:15:29.260026+02:00] database.INFO: SELECT *                         FROM information_schema.columns                         JOIN pg_type                         ON (pg_type.typname = columns.udt_name)                         WHERE table_name = 'user' {"elapsed":0.0107879638671875} []
[2020-01-28T17:15:29.262229+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{3}' {"elapsed":0.0008358955383300781} []
[2020-01-28T17:15:29.262498+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{4}' {"elapsed":0.00017714500427246094} []
[2020-01-28T17:15:29.262705+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{5}' {"elapsed":0.000125885009765625} []
[2020-01-28T17:15:29.262953+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{6}' {"elapsed":0.00016999244689941406} []
[2020-01-28T17:15:29.263203+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{7}' {"elapsed":0.0001709461212158203} []
[2020-01-28T17:15:29.263461+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{8}' {"elapsed":0.00017905235290527344} []
[2020-01-28T17:15:29.263653+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{9}' {"elapsed":0.00011396408081054688} []
[2020-01-28T17:15:29.263835+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{12}' {"elapsed":9.799003601074219e-5} []
[2020-01-28T17:15:29.263956+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{13}' {"elapsed":5.793571472167969e-5} []
[2020-01-28T17:15:29.264073+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{15}' {"elapsed":5.412101745605469e-5} []
[2020-01-28T17:15:29.264202+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{2}' {"elapsed":5.2928924560546875e-5} []
[2020-01-28T17:15:29.264315+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{3}' {"elapsed":5.1975250244140625e-5} []
[2020-01-28T17:15:29.264422+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{4}' {"elapsed":5.316734313964844e-5} []
[2020-01-28T17:15:29.264529+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{5}' {"elapsed":5.078315734863281e-5} []
[2020-01-28T17:15:29.264627+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{6}' {"elapsed":5.0067901611328125e-5} []
[2020-01-28T17:15:29.264723+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{7}' {"elapsed":4.887580871582031e-5} []
[2020-01-28T17:15:29.264828+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{9}' {"elapsed":5.0067901611328125e-5} []
[2020-01-28T17:15:29.264927+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{11}' {"elapsed":4.9114227294921875e-5} []
[2020-01-28T17:15:29.265030+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{15}' {"elapsed":4.9114227294921875e-5} []
[2020-01-28T17:15:29.265143+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{3}' {"elapsed":5.0067901611328125e-5} []
[2020-01-28T17:15:29.265239+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{4}' {"elapsed":4.887580871582031e-5} []
[2020-01-28T17:15:29.265335+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{5}' {"elapsed":4.887580871582031e-5} []
[2020-01-28T17:15:29.265430+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{6}' {"elapsed":4.9114227294921875e-5} []
[2020-01-28T17:15:29.265526+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{7}' {"elapsed":4.887580871582031e-5} []
[2020-01-28T17:15:29.265621+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{8}' {"elapsed":4.9114227294921875e-5} []
[2020-01-28T17:15:29.265721+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{9}' {"elapsed":5.2928924560546875e-5} []
[2020-01-28T17:15:29.265829+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{12}' {"elapsed":5.1021575927734375e-5} []
[2020-01-28T17:15:29.265926+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{13}' {"elapsed":4.9114227294921875e-5} []
[2020-01-28T17:15:29.266038+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 17802 AND contype = 'c' AND conkey = '{15}' {"elapsed":5.412101745605469e-5} []
[2020-01-28T17:15:29.267647+02:00] database.INFO: SELECT * FROM pg_indexes WHERE schemaname = 'f_data' AND tablename = 'user' {"elapsed":0.0015099048614501953} []
[2020-01-28T17:15:29.267911+02:00] database.INFO: SELECT contype FROM pg_constraint WHERE conname = 'user_pkey' {"elapsed":0.0001780986785888672} []
[2020-01-28T17:15:29.582315+02:00] database.INFO: SELECT tc.constraint_name, tc.table_name, kcu.column_name, rc.update_rule, rc.delete_rule, ccu.table_name AS foreign_table_name, ccu.column_name AS foreign_column_name FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu    ON tc.constraint_name = kcu.constraint_name JOIN information_schema.constraint_column_usage AS ccu    ON ccu.constraint_name = tc.constraint_name JOIN information_schema.referential_constraints AS rc    ON rc.constraint_name = tc.constraint_name WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = 'user' {"elapsed":0.31433701515197754} []
[2020-01-28T17:15:29.583371+02:00] database.INFO: SELECT * FROM pg_indexes WHERE schemaname = 'f_data' AND tablename = 'user' {"elapsed":0.0006330013275146484} []
[2020-01-28T17:15:29.583596+02:00] database.INFO: SELECT contype FROM pg_constraint WHERE conname = 'user_pkey' {"elapsed":0.00014495849609375} []
[2020-01-28T17:15:29.584425+02:00] database.INFO: INSERT INTO "f_data"."user" ("id", "role_id", "email", "new_email", "username", "password", "auth_key", "api_key", "share_key", "share_track", "ban_time", "ban_reason", "login_ip", "login_time", "create_ip", "status", "create_time", "update_time") VALUES (724, 5, '[email protected]', NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, '2020-01-28T17:15:29+02:00', '2020-01-28T17:15:29+02:00') RETURNING "id" {"elapsed":0.00036716461181640625} []
[2020-01-28T17:15:29.585477+02:00] database.INFO: SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema = 'f_data'           AND table_type = 'BASE TABLE' AND table_name = 'user_profile' {"elapsed":0.0005700588226318359} []
[2020-01-28T17:15:29.586146+02:00] database.INFO: SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema = 'f_data'           AND table_type = 'BASE TABLE' AND table_name = 'user_profile' {"elapsed":0.0005431175231933594} []
[2020-01-28T17:15:29.586443+02:00] database.INFO: SELECT oid FROM pg_class WHERE relname = 'user_profile' {"elapsed":0.0002357959747314453} []
[2020-01-28T17:15:29.591299+02:00] database.INFO: SELECT *                         FROM information_schema.columns                         JOIN pg_type                         ON (pg_type.typname = columns.udt_name)                         WHERE table_name = 'user_profile' {"elapsed":0.004782915115356445} []
[2020-01-28T17:15:29.591772+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 16628 AND contype = 'c' AND conkey = '{3}' {"elapsed":0.00021910667419433594} []
[2020-01-28T17:15:29.592009+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 16628 AND contype = 'c' AND conkey = '{4}' {"elapsed":0.00015306472778320312} []
[2020-01-28T17:15:29.592246+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 16628 AND contype = 'c' AND conkey = '{5}' {"elapsed":0.00015807151794433594} []
[2020-01-28T17:15:29.592483+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 16628 AND contype = 'c' AND conkey = '{6}' {"elapsed":0.0001590251922607422} []
[2020-01-28T17:15:29.592717+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 16628 AND contype = 'c' AND conkey = '{7}' {"elapsed":0.0001571178436279297} []
[2020-01-28T17:15:29.592919+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 16628 AND contype = 'c' AND conkey = '{8}' {"elapsed":0.0001239776611328125} []
[2020-01-28T17:15:29.593079+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 16628 AND contype = 'c' AND conkey = '{3}' {"elapsed":7.891654968261719e-5} []
[2020-01-28T17:15:29.593216+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 16628 AND contype = 'c' AND conkey = '{4}' {"elapsed":7.510185241699219e-5} []
[2020-01-28T17:15:29.593349+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 16628 AND contype = 'c' AND conkey = '{5}' {"elapsed":7.605552673339844e-5} []
[2020-01-28T17:15:29.593485+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 16628 AND contype = 'c' AND conkey = '{6}' {"elapsed":7.486343383789062e-5} []
[2020-01-28T17:15:29.593595+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 16628 AND contype = 'c' AND conkey = '{7}' {"elapsed":5.888938903808594e-5} []
[2020-01-28T17:15:29.593696+02:00] database.INFO: SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint         WHERE conrelid = 16628 AND contype = 'c' AND conkey = '{8}' {"elapsed":5.1975250244140625e-5} []
[2020-01-28T17:15:29.594383+02:00] database.INFO: SELECT * FROM pg_indexes WHERE schemaname = 'f_data' AND tablename = 'user_profile' {"elapsed":0.0006208419799804688} []
[2020-01-28T17:15:29.594575+02:00] database.INFO: SELECT contype FROM pg_constraint WHERE conname = 'user_profile_pkey' {"elapsed":0.00014495849609375} []
[2020-01-28T17:15:29.903750+02:00] database.INFO: SELECT tc.constraint_name, tc.table_name, kcu.column_name, rc.update_rule, rc.delete_rule, ccu.table_name AS foreign_table_name, ccu.column_name AS foreign_column_name FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu    ON tc.constraint_name = kcu.constraint_name JOIN information_schema.constraint_column_usage AS ccu    ON ccu.constraint_name = tc.constraint_name JOIN information_schema.referential_constraints AS rc    ON rc.constraint_name = tc.constraint_name WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = 'user_profile' {"elapsed":0.30912208557128906} []
[2020-01-28T17:15:29.904518+02:00] database.INFO: SELECT * FROM pg_indexes WHERE schemaname = 'f_data' AND tablename = 'user_profile' {"elapsed":0.0006289482116699219} []
[2020-01-28T17:15:29.904743+02:00] database.INFO: SELECT contype FROM pg_constraint WHERE conname = 'user_profile_pkey' {"elapsed":0.00014519691467285156} []
[2020-01-28T17:15:29.905470+02:00] database.INFO: INSERT INTO "f_data"."user_profile" ("fname", "lname", "phone", "mobile", "email", "lang", "create_time", "update_time", "user_id") VALUES (NULL, NULL, NULL, NULL, NULL, NULL, '2020-01-28T17:15:29+02:00', '2020-01-28T17:15:29+02:00', 724) RETURNING "id" {"elapsed":0.0005609989166259766} []
[2020-01-28T17:15:29.905599+02:00] database.INFO: Commit transaction [] []

What could be the problem ?

Custom type

Hi. If I want use some custom types of Postgres? For example "geometry" (postgis).

Try this?

class Uuid implements ValueInterface
{
    /** @var UuidBody */
    private $uuid;

    /**
     * @return string
     */
    public function rawValue(): string
    {
        return $this->uuid->getBytes();
    }

    ...

Try psalm.dev

Would be interesting to see how it will process Cycle codebase.

Improve the performance even more (part 2/3)

Based on this benchmark https://github.com/adrianmiu/forked-php-orm-benchmark the ORM is pretty slow currently compared to competitors.

The reference test:
Без названия

Current results:
Screenshot_136

The ideal optimizations based on cached nodes and generated mappers looks like:
Screenshot_69

Need to abstract Node Parsers into its own factory so:

  1. They can be cached (the structure does not change between requests)
  2. They can be generated (to be benched, but in theory, it should produce even better results).

The persist most likely can't be optimized a lot due to the nature of the graph traversal algorithm.

Strange error in column wrapper

Hi. Help me with column wrapper.

I wanna use Postgis in my project with Laravel 6.x

I've created column wrapper for Point (Geometry, srid 4326) type

<?php declare(strict_types=1);

namespace App\Domain\ColumnWrappers;

use Spiral\Database\DatabaseInterface;

class Point
{
    /**
     * @var \Phaza\LaravelPostgis\Geometries\Point
     */
    private $point;

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

    /**
     * @param $lat
     * @param $lng
     * @param  null  $alt
     * @return Point
     */
    public static function create($lat, $lng, $alt = null): Point
    {
        $point = new static();
        $point->point = new \Phaza\LaravelPostgis\Geometries\Point($lat, $lng, $alt);

        return $point;
    }

    /**
     * @param string            $value
     * @param DatabaseInterface $db
     * @return Point
     */
    public static function typecast($value, DatabaseInterface $db): Point
    {
        $point = new static();
        $point->point = \Phaza\LaravelPostgis\Geometries\Point::fromString((string)$value);

        return $point;
    }
}

then add in model

/** @Cycle\Column(type="string", typecast="Point") */
protected $location; 

in DB stored data looks like

0101000020E61000001EE0490B97B53D40603B18B14FF64D40

column type === geography(Point, 4326)

But I see error every time

Unable to parse incoming row: Unable to typecast location

This is schema dump

Cycle\ORM\Schema {#549
  -aliases: array:3 [
    "App\Domain\Models\ORM\Poi" => "poi"
    "App\Domain\Models\ORM\PoiGeosetting" => "poiGeosetting"
    "App\Domain\Models\ORM\PoiPrice" => "poiPrice"
  ]
  -schema: array:3 [
    "poi" => array:13 [
      1 => "App\Domain\Models\ORM\Poi"
      2 => "Cycle\ORM\Mapper\Mapper"
      3 => "Cycle\ORM\Select\Source"
      4 => "Cycle\ORM\Select\Repository"
      5 => "default"
      6 => "poi"
      7 => "contract_number"
      8 => array:1 [
        0 => "contract_number"
      ]
      9 => array:18 [
        "contract_number" => "contract_number"
        "contract_name" => "contract_name"
        "status" => "status"
        "country_code" => "country_code"
        "region_code" => "region_code"
        "secession_gpn" => "secession_gpn"
        "belongs_to" => "belongs_to"
        "name" => "name"
        "partner" => "partner"
        "brand" => "brand"
        "own_type" => "own_type"
        "latitude" => "latitude"
        "longitude" => "longitude"
        "type" => "type"
        "location_type" => "location_type"
        "time_zone" => "time_zone"
        "updated_at" => "updated_at"
        "balloon_type" => "balloon_type"
      ]
      10 => array:2 [
        "geosettings" => array:4 [
          0 => 10
          1 => "poiGeosetting"
          3 => 10
          2 => array:4 [
            30 => true
            31 => false
            33 => "contract_number"
            32 => "poi_contract_number"
          ]
        ]
        "prices" => array:4 [
          0 => 11
          1 => "poiPrice"
          3 => 10
          2 => array:5 [
            30 => true
            31 => false
            41 => []
            33 => "contract_number"
            32 => "poi_contract_number"
          ]
        ]
      ]
      12 => null
      13 => array:4 [
        "region_code" => "int"
        "latitude" => "float"
        "longitude" => "float"
        "updated_at" => "datetime"
      ]
      14 => []
    ]
    "poiGeosetting" => array:13 [
      1 => "App\Domain\Models\ORM\PoiGeosetting"
      2 => "Cycle\ORM\Mapper\Mapper"
      3 => "Cycle\ORM\Select\Source"
      4 => "Cycle\ORM\Select\Repository"
      5 => "default"
      6 => "poi_geo_settings"
      7 => "poi_contract_number"
      8 => array:1 [
        0 => "poi_contract_number"
      ]
      9 => array:5 [
        "poi_contract_number" => "poi_contract_number"
        "small_radius" => "small_radius"
        "large_radius" => "large_radius"
        "max_detour" => "max_detour"
        "location" => "location"
      ]
      10 => []
      12 => null
      13 => array:4 [
        "small_radius" => "int"
        "large_radius" => "int"
        "max_detour" => "int"
        "location" => array:2 [
          0 => "\App\Domain\ColumnWrappers\Point"
          1 => "typecast"
        ]
      ]
      14 => []
    ]
    "poiPrice" => array:13 [
      1 => "App\Domain\Models\ORM\PoiPrice"
      2 => "Cycle\ORM\Mapper\Mapper"
      3 => "Cycle\ORM\Select\Source"
      4 => "Cycle\ORM\Select\Repository"
      5 => "default"
      6 => "poi_prices"
      7 => "id"
      8 => array:1 [
        0 => "id"
      ]
      9 => array:8 [
        "id" => "id"
        "poi_contract_number" => "poi_contract_number"
        "fuel_code" => "fuel_code"
        "price" => "price"
        "currency" => "currency"
        "currency_code" => "currency_code"
        "date_from" => "date_from"
        "date_to" => "date_to"
      ]
      10 => []
      12 => null
      13 => array:5 [
        "id" => "int"
        "price" => "float"
        "currency_code" => "int"
        "date_from" => "datetime"
        "date_to" => "datetime"
      ]
      14 => []
    ]
  ]
}

PHPStorm issue

During the development in PHPStorm it was detected problem with usage Cycle\ORM\Select

IDE can't work correctly with annotation $this for defined methods like where, andWhere, orderBy, etc:

private function someQuery(string $fieldVal): Select
{
    $repository->select()
                ->where('field', 'like', $fieldVal);
}

This code will be highlighted as wrong with error message like "Return value expected to be '\Cycle\ORM\Select'. '$this' returned"

It breaks auto-completion and hinting.

It works correctly with annotation self

Add support for inner Selects in SQL generation for joined entities

Example of generated SQL:

SELECT table1_alias.* FROM
(
    SELECT id, field1,  field2 from table1
    WHERE field1=%val1
    LIMIT 10 OFFSET 20
) as table1_alias
LEFT JOIN (
SELECT field3, field4, table1_id FROM table2
WHERE field3=%val2
LIMIT 10 OFFSET 20
) as table2_alias ON table1_alias.id = table2_alias.table1_id

Cannot load related entities through many to many relations via LEFT_JOIN

$select->load('many_to_many.has_many') failed with Undefined node 'has_many' exception.

$select->load('[email protected]_many') failed with Undefined relation 'pivotTestModel'.'has_many' exception.

This problem in AbstractLoader#251 in $node->getNode($relation) function.

In debug $node has this structure:

image

Here my models

/**
 * @Entity
 */
class ManyToManyTestModel extends BaseTestModel
{
    public function __construct(array $data = [])
    {
        $this->many_to_many_inverse = new PivotedCollection();
        $this->has_many = new ArrayCollection();
        parent::__construct($data);
    }

    /**
     * @ManyToMany(though=PivotTestModel::class, target=HasManyTestModel::class)
     */
    public $many_to_many_inverse;

    /** @HasMany(target=RelatedModel::class, nullable=true) */
    public $has_many;
}

/**
 * @Entity
 */
class HasManyTestModel extends BaseTestModel
{
    public function __construct(array $data = [])
    {
        $this->many_to_many = new PivotedCollection();

        parent::__construct($data);
    }

    /**
     * @ManyToMany(target=ManyToManyTestModel::class, though=PivotTestModel::class)
     */
    public $many_to_many;

    public $has_many_inverse;
}

/**
 * @Entity
 */
class PivotTestModel
{
    /**  @Column(type="primary") */
    public $id;
}

/**
 * @Entity
 */
class RelatedModel extends BaseTestModel
{
}

Add support for custom persist commands

Need addCommand method in transaction. Possibly add ability to automatically link to parent context (some user friendly simplification?). Need ability to expose column name resolution for update/delete queries (see resolveAlias of QueryBuilder, split query builder?).

Primary key

if ($this->getPrimary($entity) === null) {

if in my table primary key === string not uuid?

now I try to do like this

/** @Cycle\Column(type = "primary", typecast = "string") */
    public $contract_number;

but get error

Cycle\ORM\Exception\ParserException: Unable to parse incoming row: Unable to typecast `contract_number`

Do not use final classes

Objective: To adapt the cycle to the existing solutions (Symfony, Laravel, е.g.)

The easiest implementation:

class MyDatabaseManager extends Spiral\Database\DatabaseManager 
{
    protected function makeDatabase(DatabasePartial $database): Database
    {
        return new Database (
            $database->getName(),
            $database->getPrefix(),
            // In this place we can create our own driver using the existing connection in the framework, like:
            // DoctrineMySQLDriver::fromPdo($this->symfony->connection($database->getName())->getPdo())
    }
}

class DoctrineMySQLDriver extends MySQLDriver { ... }

But such an implementation is impossible, we cannot reuse the existing connection due to the presence of the final classes in the code.

DatabaseManager doesn't even have an interface so we can completely rewrite it, and the code uses impl: https://github.com/cycle/orm/blob/master/src/Factory.php#L42

Proposal:

  1. Use interfaces at "interaction" points, like:
interface DatabaseManagerInterface 
{
    public function getDatabases(): iterable;
    public function database(string $database = null): Database;
    public function addDatabase(Database $database);
}
  1. Delete and do not use the final keyword.
  2. Other ideas how to adapt the configuration of Doctrine or Eloquent to reuse it in a cycle?

Constrain -> Scope

Constrain is not a good name for a thing since it is a verb. I think it should "constraint".

Probably bindParameters bug

ORM Version: 1.0.3

Code

$database = $ormFactory->database('example');
$database->execute('SHOW TABLES');

Expected

All OK

Actual

Argument 1 passed to Spiral\Database\Driver\Driver::bindParameters() must be an instance of Spiral\Database\Statement, instance of Doctrine\DBAL\Driver\PDOStatement given, called in .../vendor/spiral/database/src/Driver/Driver.php on line 275

image

I'm still trying to understand the cause of the problem. Maybe I'm doing something wrong =))))

fetchOne() overrides current entity

I do not know this is by design or not, but I think it is a strange behavior.

This is my use case:

I have an entity, and I would like to check this entity exists or not in the database. A simplified example:

class CustomerRepository
{
    public function exists($entity)
    {
        /** @var \Cycle\ORM\Select $select */
        $select = $this->select();
        // .. adding conditions based on primary keys
        return $this->fetchOne() !== null;
    }
}

After calling fetchOne my $entity instance is overridden with the result of fetchOne

Add support for composite PK and relations [9/16]

What is currently done:

  • ability to generate schema with composite PK
  • ability to persist entity with composite PK (PK generation is application layer specific)
  • ability to request entity by composite key from Repository, ORM* and Heap*
  • ability to carry N dependencies in the command chain
  • ability to fetch by multiple columns/key in SQL builders/loaders
  • ability to carry complex context via Reference (yay)
  • support for DBAL to declare composite FKs (no BC expected, deprecation will be required)
  • train schema-builder to automatically define composite inner/outer keys
  • scanning logic in ORM and Heap to select by composite key (alternative - optimize on index level)

What needs to be done:

  • ability to collect complex reference in Parser/Node (no BC expected, can be done via combined references "key1.key2.key3...")
  • ability to define complex reference in relations (no BC expected)
  • ability to quickly index entity based on composite PK in ORM and Heap (no BC expected)
  • disable 1 PK validation
  • ensure that composite PKs always require app specific values (DatabaseMapper level)
  • improve relations to support composite inner/outer keys (schema update)
  • minor optimizations in context producers and consumers to simplify binding the dependency on multiple values (memory optimization)

PSR-12 compatibility

I think that you can follow the PSR-12, which should soon become stable: https://github.com/php-fig/fig-standards/blob/master/proposed/extended-coding-style-guide.md#3-declare-statements-namespace-and-import-statements

Actual

<?php declare(strict_types=1);
/**
 * Spiral Framework.
 *
 * @license   MIT
 * @author    Anton Titov (Wolfy-J)
 */

namespace Cycle\ORM;

Expected

<?php
/**
 * Spiral Framework.
 *
 * @license   MIT
 * @author    Anton Titov (Wolfy-J)
 */
declare(strict_types=1);

namespace Cycle\ORM;

Optimistic lock support

Simple mapper modification to issue different command set for queue update and queue delete with version check.

Remove zendframework/zend-hydrator from dependencies.

Package zendframework/zend-stdlib is abandoned, you should avoid using it. Use laminas/laminas-stdlib instead.
Package zendframework/zend-hydrator is abandoned, you should avoid using it. Use laminas/laminas-hydrator instead.

cycle\orm requires zendframework/zend-hydrator, while zendframework/zend-hydrator requires zendframework/zend-stdlib.

These two packages are officially abandoned.

add `andFilterWhere` condition

It would be a nice feature to write code like this:

// grid filtering conditions
$select->andFilterWhere('code', 'like', $entity->code);
$select->andFilterWhere('name', 'like', $entity->name);

instead of this:

// grid filtering conditions
if ($entity->code !== null) {
    $select->andWhere('code', 'like', $entity->code);
}
if ($entity->name !== null) {
    $select->andWhere('name', 'like', $entity->name);
}

I tried to implement it, but I do not really understand yet how it works.

Update relations via set of ids or something like that

I need to update HasMany and ManyToMany relations via set of ids, instead of loading entities that exists in database.

For example:

Child table

Id Parent id field
1 1 1
2 1 2
3 null 5
$parent->children->sync([1,3]);
$parent->save();

Child table

Id Parent id field
1 1 1
2 null 2
3 1 5

ORM should generate 2 update queries for nullable hasMany relations and 1 update and 1 delete query for not nullable hasMany relations and ManyToMany relations.

UPDATE child SET parent_id = null
WHERE id IN (SELECT unnest(?) EXCEPT
SELECT child.id FROM child WHERE child.parent_id=?);

DELETE FROM child 
WHERE id IN (SELECT unnest(?) EXCEPT
SELECT child.id FROM child WHERE child.parent_id=?);

UPDATE child SET parent_id = ?
WHERE child.id IN ?;

Laravel

Hi. Can I use your ORM with Laravel/Lumen framework?

Entity inheritance

My classes definition:

** @Entity */
class User
{
    /** @Column(type="primary") */
    public $id;
}

/** @Entity */
class CustomUser extends User
{
    /** @Column(type="string") */
    public $email;
}

DI container configuration

$container = new Container();
$container->setDefinitions([ User::class => CustomUser::class ]);

Finding entity with class defined in DI container

$definitions = $container->getDefinitions();
$definition = $definitions[User::class];
// $definition is equal ['class' => 'path\to\CustomUser']

$entity = $orm->getRepository($definition['class'])->findOne([...]);

Now I try to get class of $entity

get_class($entity);

I expecting to get CustomUser but got User

Support custom collection types

Currently, this is the only thing which strictly couples the domain layer to the ORM. Can be achieved by adding CollectionFactory and invoking it in a relation initialization. Need proper support for pivot collections and collection data extraction.

Ability to replace or disable default scope on load

#51

$select->load('comments', ['load' => new PublicCmments()])

Now it works so that the new Scope (Constraint) is added to a default global Scope.

It would be very cool to be able to replace or disable a default Scope

set logger to all drivers via `DBAL->setLogger()`

The DatabaseManager class uses the LoggerTrait trait
He has the setLogger($logger) method

If i want to log SQL queries then i call setLogger() method on DatabaseManager object
But all existing drivers do not recieve LoggerInterface because drivers were created when created the DatabaseManager instance

It would be great if instead of this code:

foreach ($dbal->getDrivers() as $driver) {
    $driver->setLogger($this->logger);
}

... i could use this:

$dbal->setLogger($this->logger);

Moving typing from entity to mapping schema

I think that types should be defined in the schema, not in Entity. In this case, we can allow for more flexible behavior (have several different types in the same entity depending on the repository source). In that case, when we need to guarantee the types in entity, we can use the PHP 7.4+ properties typing.

Actual

class Entity
{
    /** @column(type=string) */
    protected $some;
}
new Schema([
    'user' => [Schema::COLUMNS  => ['some']]
]);

Expected

class Entity
{
    protected $some;
}
new Schema([
    'user' => [Schema::COLUMNS  => ['some' => 'string']]
]);

Cannot creating relation between entities that have different folders

I recently used the ORM Cycle to make my task easier. But when I create 2 entities that have relationships in different folders, I get an error. The error says that

"Cannot complete the target` relations`.`city` relation (not found or invalid) "

But when I put all the entities in one folder, I didn't get any errors.

This is my Venue entity code in the App\Domain\Venue\Entities folder :

<?php

namespace App\Domain\Venue\Entities;

use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Table;
use Cycle\Annotated\Annotation\Relation\BelongsTo;
use Cycle\Annotated\Annotation\Relation\HasMany;
use Cycle\Annotated\Annotation\Table\Index;
use App\Domain\Region\Entities\City;

/**
 * @Entity(table = "venue", database = "main")
 * @Table(
 *      indexes = {
 *          @Index(columns = {"name"}),
 *          @Index(columns = {"published"}),
 *          @Index(columns = {"latitude"}),
 *          @Index(columns = {"longitude"}),
 *      }
 * )
 */
class Venue {
    /** @Column(type = "primary", name = "ID") */
    protected $id;

    //....

    /**
     * @HasMany(target = "VenueImage", innerKey = "id", outerKey = "venueId")
     */
    protected $images;

    /**
     * @BelongsTo(target = "City", innerKey = "cityId", outerKey = "id")
     */
    protected $city;

    //....
}

This is my City entity code in the App\Domain\Region\Entities folder :

<?php

namespace App\Domain\Region\Entities;

use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Table;
use Cycle\Annotated\Annotation\Relation\BelongsTo;
use Cycle\Annotated\Annotation\Table\Index;

/**
 * @Entity(table = "city", database = "region")
 * @Table(
 *      indexes = {
 *          @Index(columns = {"name"}, unique = true),
 *          @Index(columns = {"governingDistrictID"})
 *      }
 * )
 */
class City {
    /** @Column(type = "primary", name = "ID") */
    protected $id;

    //...
}

Feature Request: Add subquery support

Example of usage:

$repository->select()->subquery([
    'alias' => function(QueryBuilder $qb){
        $qb->from($tableName)->columns('field1', 'field2');
    }
]);

$repository->select()->subquery(function(QueryBuilder $qb){
        $qb->from($tableName)->columns('field1', 'field2');
    });

$repository->select()->subquery(function(QueryBuilder $qb) use($anotherSelect){
    $qb->addSelect($anotherSelect);
});

$repository->select()->with([
    'load' => function($qb){
        $qb->subquery(<...>);
    }
]);

Need to be done:

  • Add possibility for generating subqueries in core
  • Sync column aliases from subquery or parent query

Filter columns in Select

Is there any opportunities for filtering columns rather than select()->buildQuery()->columns()?

I make some investigation and found that this behaviour can be implemented via implementation of LoaderInterface and so on.

Am I right or in this library exists other way to do what I want?

I want this behaviour:

class TestModel
{
    /** @Column(type="string")*/
    public $field1;
    /** @Column(type="string")*/
    public $field2;
    /** @Column(type="string")*/
    public $field3;
    
    public function __construct($field1, $field2, $field3)
    {
        $this->field1 = $field1;
        $this->field2 = $field2;
        $this->field3 = $field3;
    }
}

class TestCase
{
     public function testSelectFields()
     {
        $t = new Transaction($orm)->persist((new TestModel('1', '2', '3')))
        $t->run();
        $result = $orm->getRepository(TestModel::class)->select()->columns(['field1', 'field2'])->fetchOne();

        $this->assertEquals('1', $result->field1);
        $this->assertEquals('2', $result->field2);
        $this->assertNull($result->field3);
     }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.