PHP DataMapper, ORM

Overview

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 daemonized 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:

  • clean and fast Data Mapper
  • ORM with has-one, has-many, many-through-many and polymorphic relations
  • Plain Old PHP objects, ActiveRecord, 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, and migrations
  • supports global query constrains, UUIDs as PK, soft deletes, auto timestamps
  • custom column types, FKs to non-primary columns
  • use with or without annotations, proxy classes, and auto-migrations
  • compatible with Doctrine Collections, Doctrine Annotations, and Zend Hydrator

Extensions:

Component Current Status
cycle/schema-builder Latest Stable Version Build Status Scrutinizer Code Quality Codecov
cycle/annotated Latest Stable Version Build Status Scrutinizer Code Quality Codecov
cycle/proxy-factory Latest Stable Version Build Status Scrutinizer Code Quality Codecov
cycle/migrations 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();

$t = new Transaction($orm);

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

$t->run();

License:

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

Comments
  • cycle\ORM\Exception\TransactionException Transaction can't be finished. Some relations can't be resolved: Update `product` - tax_rate (Cycle\ORM\Relation\BelongsTo)🐛

    cycle\ORM\Exception\TransactionException Transaction can't be finished. Some relations can't be resolved: Update `product` - tax_rate (Cycle\ORM\Relation\BelongsTo)🐛

    No duplicates 🥲.

    • [X] I have searched for a similar issue in our bug tracker and didn't find any solutions.

    What happened?

    Ubuntu 22.04 LTS Testing and Wampserver 3.2.9 Testing Above error common to both Windows and Ubuntu Apache2 using fork rossaddison/yii-invoice. Event: A new product, not used in any quote, or invoice, when tax_rate_id BelongsTo dropdown edited and saved. Also if the product's family group BelongsTo relation changes, and is saved, same error occurs.

    This error occurs when the record is edited, not created.

    This error did not occur when I was using annotations instead of the current Atributes. This issue is similar to issue #329

    Here is the entity that is being used:

    <?php
    
    declare(strict_types=1);
    
    namespace App\Invoice\Entity;
    
    use Cycle\Annotated\Annotation\Column;
    use Cycle\Annotated\Annotation\Entity;
    use App\Invoice\Entity\Family;
    use App\Invoice\Entity\TaxRate;
    use App\Invoice\Entity\Unit;
    use Cycle\Annotated\Annotation\Relation\BelongsTo;
    
    #[Entity(repository: \App\Invoice\Product\ProductRepository::class)]
    class Product
    {
        #[Column(type: 'primary')]
        private ?int $id = null;
            
        #[Column(type: 'text', nullable: true)]
        private ?string $product_sku = '';
        
        #[Column(type: 'text', nullable: true)]
        private ?string $product_name = '';
        
        #[Column(type: 'longText', nullable: false)]
        private ?string $product_description = '';
            
        #[Column(type: 'decimal(20,2)', nullable: true)]
        private ?float $product_price = null;
        
        #[Column(type: 'decimal(20,2)', nullable: true)]
        private ?float $purchase_price = null;
        
        #[Column(type: 'text', nullable: true)]
        private ?string $provider_name = '';
        
        #[BelongsTo(target:Family::class, nullable: false, fkAction: "NO ACTION")]
        private ?Family $family = null;        
        #[Column(type: 'integer(11)', nullable: true)]
        private ?int $family_id = null;
        
        // A product has to have a tax rate before it can be created even if it is a zero tax rate    
        #[BelongsTo(target:TaxRate::class, nullable: false, fkAction: "NO ACTION")]
        private ?TaxRate $tax_rate = null;    
        #[Column(type: 'integer(11)', nullable: false)]
        private ?int $tax_rate_id = null;
            
        #[BelongsTo(target:Unit::class, nullable: false, fkAction: "NO ACTION")]
        private ?Unit $unit = null;    
        #[Column(type: 'integer(11)', nullable: true)]
        private ?int $unit_id = null;
        
        #[Column(type: 'integer(11)', nullable: true)]
        private ?int $product_tariff = null;
        
        public function __construct(
            string $product_sku = '',
            string $product_name = '',
            string $product_description = '',
            float $product_price = null,
            float $purchase_price = null,
            string $provider_name = '',
            int $product_tariff = null,
            int $tax_rate_id = null,
            int $family_id = null,
            int $unit_id = null            
        )
        {
            $this->product_sku = $product_sku;  
            $this->product_name = $product_name;
            $this->product_description = $product_description;
            $this->product_price = $product_price;
            $this->purchase_price = $purchase_price;
            $this->provider_name = $provider_name;
            $this->product_tariff = $product_tariff;
            $this->tax_rate_id = $tax_rate_id;
            $this->family_id = $family_id;
            $this->unit_id = $unit_id;
        }
        //relation $family
        public function getFamily(): ?Family
        {
            return $this->family;
        }
        //relation $tax_rate
        public function getTaxrate(): ?TaxRate
        {
            return $this->tax_rate;
        }
        //relation $unit
        public function getUnit(): ?Unit
        {
            return $this->unit;
        }    
        
        public function getProduct_id(): ?string
        {
            return (string)$this->id;
        }
        
        public function setFamily_id(int $family_id): void
        {
            $this->family_id = $family_id;
        }
        
        public function getProduct_sku(): string
        {
            return $this->product_sku;
        }
        
        public function setProduct_sku(string $product_sku): void
        {
            $this->product_sku = $product_sku;
        }
        
        public function getProduct_name(): string
        {
            return $this->product_name;
        }
        
        public function setProduct_name(string $product_name): void
        {
            $this->product_name = $product_name;
        }
        
        public function getProduct_description(): string
        {
            return $this->product_description;
        }
        
        public function setProduct_description(string $product_description): void
        {
            $this->product_description = $product_description;
        }
    
        public function getProduct_price(): float
        {
            return $this->product_price;
        }
        
        public function setProduct_price(float $product_price): void
        {
            $this->product_price = $product_price;
        }
        
        public function getPurchase_price(): float
        {
            return $this->purchase_price;
        }
        
        public function setPurchase_price(float $purchase_price): void
        {
            $this->purchase_price = $purchase_price;
        }
        
        public function getProvider_name(): string
        {
            return $this->provider_name;
        }
        
        public function setProvider_name(string $provider_name): void
        {
            $this->provider_name = $provider_name;
        }
    
        public function setTax_rate_id(int $tax_rate_id): void
        {
            $this->tax_rate_id = $tax_rate_id;
        }
    
        public function getTax_rate_id(): ?int
        {
            return $this->tax_rate_id;
        }
    
        public function setUnit_id(int $unit_id): void
        {
            $this->unit_id = $unit_id;
        }
    
        public function getUnit_id(): ?int
        {
            return $this->unit_id;
        }
    
        public function getFamily_id(): ?int
        {
            return $this->family_id;
        }
        
        public function getProduct_tariff(): int
        {
            return $this->product_tariff;
        }
    
        public function setProduct_tariff(int $product_tariff): void
        {
            $this->product_tariff = $product_tariff;
        }
    }
    

    Version

    ORM 2.0.0 
    Ubuntu PHP 8.1.2, Wampserver PHP 8.1.7
    
    type:bug status:to be verified 
    opened by rossaddison 36
  • Typed property must not be accessed before initialization

    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.

    type:question magic 
    opened by armpogart 18
  • Недостаточный контроль над relations

    Недостаточный контроль над relations

    Всем привет!

    Есть две сущности, одна наследует другую (STI), в этих сущностях используются типизированные свойства и в relation явно указывается на что ссылается Relation

    class User {
        /** @ORM\Relation\BelongsTo(target="Studio", nullable=true)*/
        protected ?Studio $studio = null;
    
        // ...
    } 
    
    class Studio extends User {
        // ...
    } 
    

    И в этой ситуации наш код перестает работать, т.к. до полной загрузки связанной сущности мы работаем с другим объектом Reference Окей, думаем как исправить это, можно пойти по пути использования своей PromiseFactory, подключаем и ..., это нам также ничем не поможет, т.к. метод promise выгляит так:

    public function promise(ORMInterface $orm, string $role, array $scope)
    

    И мы можем узнать только то, что нам пришла роль user и ee scope также не содержит подсказки с чем имеем дело.

    Вообще идея у меня была следующая, зная target можно было бы создать пустую сущность и предзаполнить все поля из scope, что помогло бы получить объект нужного типа. Т.е. фабрика нам позволяет поменять Reference на что-то своё, но полного контроля не дает.

    Окей, думаем дальше. У вас есть пакет cycle/proxy-factory , он также не сможет создать правильный объект, т.к. он не знает что он имеет дело с STI, создаст просто User и код сломается.

    Я вижу два пути выхода из положения

    1. Передавать каким либо образом текущий Relation и корректный target в нем, чтобы понимать что ожидается
    2. Давать возможность в анотации или схеме указывать класс, который мы ожидаем на выходе.

    P.S. final class это реально зло! Чтобы немного доработать сущестующий код, мне приходится целые классы тащить за собой, вместо переопределения одного метода.

    type:enhancement 
    opened by butschster 15
  • ManyToMany error Transaction can't be finished. Some relations can't be resolved🐛

    ManyToMany error Transaction can't be finished. Some relations can't be resolved🐛

    What happened?

    У меня есть две сущности `User` и `Tag` со связью `ManyToMany`.

    Если в обоих сущностях указать связь ManyToMany, то возникает ошибка Transaction can't be finished. Some relations can't be resolved

    Для воспроизведения ошибки, я взял ваш тест Cycle\ORM\Tests\Functional\Driver\Common\Relation\ManyToMany\ManyToManyRelationTest и добавил в метод getSchemaArray() связь ManyToMany для Tag::class:

    I have two entities User and Tag related with the ManyToMany relation

    If the ManyToMany relation is specified in both entities, then an error will be thrown with the message Transaction can't be finished. Some relations can't be resolved

    To reproduce that case you can use the Cycle\ORM\Tests\Functional\Driver\Common\Relation\ManyToMany\ManyToManyRelationTest test case and add the ManyToMany relation for the Tag::class (in the getSchemaArray() method) as below:

             Tag::class => [
                    ...
                    Schema::RELATIONS => [
                        'users' => [
                            Relation::TYPE => Relation::MANY_TO_MANY,
                            Relation::TARGET => User::class,
                            Relation::SCHEMA => [
                                Relation::CASCADE => true,
                                Relation::THROUGH_ENTITY => TagContext::class,
                                Relation::INNER_KEY => 'id',
                                Relation::OUTER_KEY => 'id',
                                Relation::THROUGH_INNER_KEY => 'tag_id',
                                Relation::THROUGH_OUTER_KEY => 'user_id',
                            ],
                        ],
                    ],
                ],
    
    Результат выполнения теста `testUnlinkManyToManyAndReplaceSome()`:

    After testUnlinkManyToManyAndReplaceSome() calling you will see that error:

    Cycle\ORM\Exception\TransactionException : Transaction can't be finished. Some relations can't be resolved:
    Create new `tag_context`
     - tag.users:tag_context (Cycle\ORM\Relation\ShadowBelongsTo)
     /app/vendor/cycle/orm/src/Exception/TransactionException.php:40
     /app/vendor/cycle/orm/src/Transaction/UnitOfWork.php:104
     /app/vendor/cycle/orm/src/Transaction.php:48
     /app/app/src/Example/Functional/Driver/Common/BaseTest.php:248
     /app/app/src/Example/Functional/Driver/Common/Relation/ManyToMany/ManyToManyRelationTest.php:403
    

    Version

    ORM 2.0.0
    PHP 8.0
    
    type:bug status:to be verified 
    opened by only-viktor 14
  • refactor: rename constrain to scope

    refactor: rename constrain to scope

    Closes #65

    • Replace "constrain" with "scope" all over the code base
    • Add BC aliases with deprecation replacement suggestion (need to ignore cs warning on alias files)
    opened by hustlahusky 13
  • Filter columns in Select

    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);
         }
    }
    
    opened by mrakolice 11
  • Использование PostgreSQL типа array

    Использование PostgreSQL типа array

    В PostgreSQL можно использовать поля для хранения массивов https://www.postgresql.org/docs/9.1/arrays.html В документации не нашел информации по добавлению кастомных типов полей, поэтому хотел быт уточнить, какие есть способоы добавления новых типов?

    opened by butschster 10
  • STI и изменение типа сущности

    STI и изменение типа сущности

    Всем привет!

    Сегодня столкнулся с новой, интересной задачкой:

    Есть три сущности:

    
    /** @ORM\Entity */
    class User {
        ...
    }
    
    /** @ORM\Entity */
    class Model extends User {
        ...
    }
    
    /** @ORM\Entity */
    class Studio extends User {
        ...
    }
    

    Допустим мне необходимо User конвертировать в Model. Самый простой способ сделать это, грубо говоря, указать нужный тип $user->_type = 'Model'. Но как только мы хотим сделать Model -> Studio, то этот способ уже не прокатит, т.к. mapper вручную _type переопределит. Полагаю единтсвенный способ сделать это - переопределить mapper и в нем этот функционал выпилить.

    По мне идеальным решением было бы иметь некий хелпер, который бы мог заниматсья конвертаций, типа переброски всех полей из одного объекта в другой и при этом корректировать Identity map, чтобы в момент persist'a объект сохранялся в базу как новая сущность.

    opened by butschster 9
  • fetchOne() overrides current entity

    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

    type:bug 
    opened by albertborsos 9
  •  Typed property must not be accessed before initialization

    Typed property must not be accessed before initialization

    Использую PHP 7.4 Обычные сущности для Many To Many. Пытаюсь связать существующие записи. Ругается на типизированное свойство в pivot entity при сохранении. Typed property App\CategoryContractor::$id must not be accessed before initialization

    /**
     * @Cycle\Entity(table="category_contractor")
     */
    class CategoryContractor
    {
        /**
         * @Cycle\Column(type="primary")
         */
        public int $id;
    }
    
    type:question status:wontfix 
    opened by Anlamas 8
  • It doesn't load relation when filtering by column of junction table

    It doesn't load relation when filtering by column of junction table

    For this schema:

        'place' => [
            Schema::ROLE        => 'place',
            Schema::ENTITY      => StrClass::class,
            Schema::MAPPER      => StdMapper::class,
            Schema::DATABASE    => 'default',
            Schema::TABLE       => 'places',
            Schema::PRIMARY_KEY => 'id',
            Schema::COLUMNS     => [
                'id',
                'name'
            ],
            Schema::TYPECAST    => [
                "id" => "int"
            ],
            Schema::SCHEMA      => [],
            Schema::RELATIONS   => [
                'members' => [
                    Relation::TYPE   => Relation::MANY_TO_MANY,
                    Relation::TARGET => 'user',
                    Relation::SCHEMA => [
                        Relation::CASCADE           => true,
                        Relation::THROUGH_ENTITY    => 'place_member',
                        Relation::INNER_KEY         => 'id',
                        Relation::OUTER_KEY         => 'id',
                        Relation::THROUGH_INNER_KEY => 'place_id',
                        Relation::THROUGH_OUTER_KEY => 'user_id'
                    ]
                ]
            ]
        ],
        'place_member' => [
            Schema::ROLE        => 'place_member',
            Schema::ENTITY      => StrClass::class,
            Schema::MAPPER      => StdMapper::class,
            Schema::DATABASE    => 'default',
            Schema::TABLE       => 'place_members',
            Schema::PRIMARY_KEY => 'id',
            Schema::COLUMNS     => [
                'id',
                'user_id',
                'place_id',
                'role_id',
                'period_id'
            ],
            Schema::TYPECAST    => [
                "id" => "int",
                'user_id' => 'int',
                'place_id' => 'int',
                'period_id' => 'int',
                'role_id' => 'int'
            ],
            Schema::SCHEMA      => [],
            Schema::RELATIONS   => [
                'role' => [
                    Relation::TYPE   => Relation::BELONGS_TO,
                    Relation::TARGET => 'roles',
                    Relation::SCHEMA => [
                        RELATION::NULLABLE => true,
                        Relation::CASCADE   => true,
                        Relation::INNER_KEY => 'role_id',
                        Relation::OUTER_KEY => 'id'
                    ]
                ]
            ]
        ]
    ]
    

    When I run this query:

        $place = $cycle
            ->getRepository('place')
            ->select()
            ->load([
                'members' => [ 'as' => 'membersOfThatPeriod', 'where' => [ '@[email protected]_id' => 2 ]], 
                '[email protected]' 
            ])
            ->wherePk(1)
            ->fetchData());
    
    echo $place->members->getPivot($place->members[0])->role; // NULL <--- WRONG!
    

    I know that the schema is OK, because if I remove the [email protected] from the query, it gives me a reference to 'role' with the right id. And doing something like:

    $item = $cycle->>getRepository('place_member')->select()->load('role')->fetchOne(); 
    echo $item->role->name; // display the name of that role corretly.
    
    • Looking at the generated queries, the table roles was never used in any of them.
    type:bug 
    opened by guilhermeaiolfi 8
  • Improve  tests/generate-case.php

    Improve tests/generate-case.php

    Now you can set case folder name (like Issue777) and provide tempate folder different from CaseTemplate

    Set folder name

    php tests/generate-case.php --case-name=Issue777
    

    output:

    Generating new test case 'Issue777'... 
    Using template files from '/home/cycle/orm/tests/ORM/Functional/Driver/Common/Integration/CaseTemplate'...
    Generating test classes for all database types...
    Skip class Cycle\ORM\Tests\Functional\Driver\Common\Inheritance\STI\StiBaseTest with abstract methods.
    Skip class Cycle\ORM\Tests\Functional\Driver\Common\Inheritance\JTI\JtiBaseTest with abstract methods.
    Processing /home/cycle/orm/tests/ORM/Functional/Driver/SQLite/Integration/Issue777/CaseTest.php
    Processing /home/cycle/orm/tests/ORM/Functional/Driver/MySQL/Integration/Issue777/CaseTest.php
    Processing /home/cycle/orm/tests/ORM/Functional/Driver/Postgres/Integration/Issue777/CaseTest.php
    Processing /home/cycle/orm/tests/ORM/Functional/Driver/SQLServer/Integration/Issue777/CaseTest.php
    Done. New test case is here:
    /home/cycle/orm/tests/ORM/Functional/Driver/Common/Integration/Issue777
    

    Set template folder

    php tests/generate-case.php --case-name=Issue888 --template=Issue777
    

    output:

    Generating new test case 'Issue888'... 
    Using template files from '/home/cycle/orm/tests/ORM/Functional/Driver/Common/Integration/Issue777'...
    Generating test classes for all database types...
    Skip class Cycle\ORM\Tests\Functional\Driver\Common\Inheritance\STI\StiBaseTest with abstract methods.
    Skip class Cycle\ORM\Tests\Functional\Driver\Common\Inheritance\JTI\JtiBaseTest with abstract methods.
    Processing /home/cycle/orm/tests/ORM/Functional/Driver/SQLite/Integration/Issue888/CaseTest.php
    Processing /home/cycle/orm/tests/ORM/Functional/Driver/MySQL/Integration/Issue888/CaseTest.php
    Processing /home/cycle/orm/tests/ORM/Functional/Driver/Postgres/Integration/Issue888/CaseTest.php
    Processing /home/cycle/orm/tests/ORM/Functional/Driver/SQLServer/Integration/Issue888/CaseTest.php
    Done. New test case is here:
    /home/cycle/orm/tests/ORM/Functional/Driver/Common/Integration/Issue888
    
    type:enhancement 
    opened by gam6itko 1
  • 🐛 FK id Column case mismatch

    🐛 FK id Column case mismatch

    No duplicates 🥲.

    • [X] I have searched for a similar issue in our bug tracker and didn't find any solutions.

    What happened?

    We have classes below

    class AClass {
    
       #[Cycle\Column(type: 'integer')]
       private int $userId;
    }
    
    class BClass {
        #[Cycle\Relation\BelongsTo(target: User::class)]
        private Exchanger $user;
    }
    
    class User {
         #[Cycle\Column(type: 'primary')]
         private ?int $id = null;
       
        // properties here
    }
    

    When I make requests like this. In the $aList the members have a userId property, and in the $bList there is a user_id property.

     $aList = $this->getOrm()->getRepository(AClass::class)
                ->select()
                ->fetchData();
    
    // $alist = [ ['userId' => 1] ]
                
     $bList = $this->getOrm()->getRepository(BClass::class)
                ->select()
                ->fetchData();
    // $bList = [ ['user_id' => 1, 'user' => ['id' => 1, 'foo' => 'bar']] ]
    

    Is it possible to do something with the #[Cycle\Column(type: 'integer')] to fetch columns in different cases in queries?

    Version

    PHP 8.1
    cycle/orm  2.2.1
    
    type:bug status:to be verified 
    opened by gam6itko 0
  • 🔍  Check compatibility with `loophp/collection` v7

    🔍 Check compatibility with `loophp/collection` v7

    opened by roxblnfk 0
  • 🐛 Several entities broken insert order

    🐛 Several entities broken insert order

    No duplicates 🥲.

    • [X] I have searched for a similar issue in our bug tracker and didn't find any solutions.

    What happened?

    If you save multiple related entities in the same transaction, there may be saved in broken order.

    $em = (new EntityManager($orm));
    $em->persist($user);
    
    $i = 1;
    $em->persist(new User\Alias($user, "{$user->age}-$i")) and $i++;
    
    $em->persist(new User\Email($user, "{$user->age}-$i")) and $i++;
    $em->persist(new User\Email($user, "{$user->age}-$i")) and $i++;
    
    $em->persist(new User\Phone($user, "{$user->age}-$i")) and $i++;
    $em->persist(new User\Phone($user, "{$user->age}-$i")) and $i++;
    $em->persist(new User\Phone($user, "{$user->age}-$i")) and $i++;
    
    $em->run();
    

    will be saved in db in broken order

    {
      "alias": [
        {
          "value": "246-1"
        }
      ],
      "email": [
        {
          "value": "246-2"
        },
        {
          "value": "246-3"
        }
      ],
      "phone": [
        {
          "value": "246-6"
        },
        {
          "value": "246-4"
        },
        {
          "value": "246-5"
        }
      ]
    }
    

    Bug demo: https://github.com/gam6itko/app/commit/70024f6c50527b2e21f25e5b3cc33217f32dd8c7

    Bug detected on: http://localhost:8080/issue1 http://localhost:8080/issue2 http://localhost:8080/issue3

    Bug NOT detected on: http://localhost:8080/no_issue1 http://localhost:8080/no_issue2

    Version

    ORM 2.2.0 
    PHP 8.1.2
    
    cycle/annotated                   v3.2.0          Cycle ORM Annotated Entities generator
    cycle/database                    2.2.2           DBAL, schema introspection, migration and pagination
    cycle/migrations                  v4.0.0          Database migrations, migration scaffolding
    cycle/orm                         v2.2.0          PHP DataMapper ORM and Data Modelling Engine
    cycle/schema-builder              v2.1.0          Cycle ORM Schema Builder
    cycle/schema-migrations-generator 3.x-dev df0e391 Cycle ORM Migration generation
    cycle/schema-renderer             1.1.0           Utils for Cycle ORM Schema rendering
    
    type:bug status:to be verified 
    opened by gam6itko 0
Releases(v2.2.1)
  • v2.2.1(Dec 1, 2022)

    What's Changed

    • Fix EM::persistState() that inserted the same entity twice by @roxblnfk (#368)
    • Fix bug on saving of replaced pivoted collection by @BelaRyc (#382)
    • Fix cascade mode in BelongsTo relation by @roxblnfk and @msmakouz (#347, #374)
    • Fix storing od embedded entities in a JTI by @butschster (#379)
    • Add tests case template by @roxblnfk and @kastahov (#372, #377)
    • Add a previous exception in TransactionException on throwing by @Eugentis (#367)
    • Add annotation @readonly for Repository::$select by @roxblnfk (#369)

    New Contributors

    • @Eugentis made their first contribution in https://github.com/cycle/orm/pull/367
    • @kastahov made their first contribution in https://github.com/cycle/orm/pull/377
    • @BelaRyc made their first contribution in https://github.com/cycle/orm/pull/382

    Full Changelog: https://github.com/cycle/orm/compare/v2.2.0...v2.2.1

    Source code(tar.gz)
    Source code(zip)
  • v2.2.0(Jul 5, 2022)

    What's Changed

    • Add supporting for loophp/collection by @drupol (#344)
    • Add supporting for PHP 8.1 Enum in the default typecast handler Cycle\ORM\Parser\Typecast by @roxblnfk (#352)
    • Improve template annotations in Cycle\ORM\Select\Repository and Cycle\ORM\Select classes by @roxblnfk (#351)
    • Classes Cycle\ORM\Transaction\UnitOfWork and Cycle\ORM\Transaction\Runner are now not internal by @roxblnfk (#353)

    New Contributors

    • @drupol made their first contribution in https://github.com/cycle/orm/pull/344

    Full Changelog: https://github.com/cycle/orm/compare/v2.1.1...v2.2.0

    Source code(tar.gz)
    Source code(zip)
  • v2.1.1(Jun 5, 2022)

    What's Changed

    • Remove $config property overriding in the RelationConfig by @msmakouz (#343)
    • Fix bug on ManyToMany resolving by @roxblnfk and @albertborsos (#345)

    New Contributors

    • @albertborsos made their first contribution in https://github.com/cycle/orm/pull/336

    Full Changelog: https://github.com/cycle/orm/compare/v2.1.0...v2.1.1

    Source code(tar.gz)
    Source code(zip)
  • v2.1.0(Mar 3, 2022)

    What's Changed

    • Remove final from the Select class by @msmakouz (#327)
    • Fix keys comparing in the BelongsTo relation by @msmakouz (#326)
    • Add Psalm @template annotations to RepositoryInterface by @roxblnfk

    Full Changelog: https://github.com/cycle/orm/compare/v2.0.2...v2.1.0

    Source code(tar.gz)
    Source code(zip)
  • v2.0.2(Jan 27, 2022)

  • v2.0.1(Jan 20, 2022)

    What's Changed

    • Fix protected relation fields hydration on eager loading by @roxblnfk in https://github.com/cycle/orm/pull/314

    Full Changelog: https://github.com/cycle/orm/compare/v2.0.0...v2.0.1

    Source code(tar.gz)
    Source code(zip)
  • v1.8.1(Jan 17, 2022)

    What's Changed

    • Add the STI discriminator autoadding in the Schema by @gam6itko (#278)
    • Extract origin in the HasMany::queue() when it's instance of Collection by @roxblnfk (#277)
    • Fix lazy loading sorting by @msmakouz (#300)
    • Up min version of laminas-hydrator by @msmakouz (#310)

    New Contributors

    • @gam6itko made their first contribution in https://github.com/cycle/orm/pull/278

    Full Changelog: https://github.com/cycle/orm/compare/v1.8.0...v1.8.1

    Source code(tar.gz)
    Source code(zip)
  • v2.0.0(Dec 22, 2021)

    • Minimal PHP version is 8.0
    • Composited keys
    • 'Joined Table Inheritance' and 'Single Table Inheritance'
    • Added ProxyMapper (Cycle\Orm\Mapper\Mapper)
    • Supporting for arrays/Doctrine/Laravel or custom collections in HasMany and ManyToMany relations
    • Typecasting moved to Mappers
    • Added Typecast handlers with Castable/Uncastable interfaces
    • Added Entity Manager and Unit Of Work instead of Cycle\ORM\Transaction
    • A lot of Interfaces are changed
    Source code(tar.gz)
    Source code(zip)
  • v1.8.0(Nov 28, 2021)

    • Added ORM::with. Other ORM::with* methods marked as deprecated (#257)
    • Better compatibility between ConstrainInterface and ScopeInterface (#271)
    Source code(tar.gz)
    Source code(zip)
  • v1.7.1(Nov 4, 2021)

  • v1.7.0(Nov 2, 2021)

    • Update the Node data comparison mechanism (#235)
    • Fix Entity data comparison with objects in fields (#234)
    • Add ability for relations to independently determine related value changing (#227)
    Source code(tar.gz)
    Source code(zip)
  • v1.6.1(Oct 13, 2021)

  • v1.6.0(Sep 8, 2021)

  • v1.5.1(Aug 6, 2021)

  • v1.5.0(Jul 1, 2021)

    • Hotfix: fixed type assertions for mapped criteria keys (#187)
    • Hotfix: inner keys naming in morphed relations (#192)
    • Refactor: prevent repeating entity hydration in the Many to Many relations (#188)
    • Added deprecation for SchemaInterface::CONSTRAIN. Use SchemaInterface::SCOPE instead (#194)
    Source code(tar.gz)
    Source code(zip)
  • v1.4.2(May 12, 2021)

  • v1.4.1(Apr 2, 2021)

  • v1.4.0(Mar 31, 2021)

    • Added support of Doctrine/Annotations v2 by @roxblnfk
    • Bugfix: prevent merge nodes with same roles when initializing pivot entity by @hustlahusky
    • Bugfix: RefersTo and BelongTo relations innerKey naming error by @roxblnfk
    • Added 'orderBy' option for relations by @roxblnfk
    • Fixed ID mapping column is set and differs from the field name by @roxblnfk
    Source code(tar.gz)
    Source code(zip)
  • v1.3.3(Feb 12, 2021)

    • fixed issue with redundant UPDATE when updating entity state in cyclic relations (related to TimestampedMapper)
    • fixed issue causing typed (7.4+) entities to fail in cycling many to many relations
    • entity re-load refreshes the entity state and relations instead of keeping the original entity
    • minor optimizations in many to many relations
    • added PHP8 test pipelines
    Source code(tar.gz)
    Source code(zip)
  • v1.3.2(Feb 4, 2021)

  • v1.3.1(Dec 24, 2020)

  • v1.3.0(Dec 23, 2020)

  • v1.2.17(Dec 4, 2020)

  • v1.2.16(Nov 27, 2020)

  • v1.2.15(Nov 2, 2020)

  • v1.2.14(Nov 2, 2020)

  • v1.2.13(Oct 23, 2020)

  • v1.2.12(Jul 30, 2020)

  • v1.2.11(Jul 22, 2020)

  • v1.2.10(Jul 16, 2020)

Owner
Cycle ORM
PHP DataMapper ORM and Data Modelling Engine by SpiralScout
Cycle ORM
[READ-ONLY] A flexible, lightweight and powerful Object-Relational Mapper for PHP, implemented using the DataMapper pattern. This repo is a split of the main code that can be found in https://github.com/cakephp/cakephp

CakePHP ORM The CakePHP ORM provides a powerful and flexible way to work with relational databases. Using a datamapper pattern the ORM allows you to m

CakePHP 146 Sep 28, 2022
Spot v2.x DataMapper built on top of Doctrine's Database Abstraction Layer

Spot DataMapper ORM v2.0 Spot v2.x is built on the Doctrine DBAL, and targets PHP 5.4+. The aim of Spot is to be a lightweight DataMapper alternative

Spot ORM 602 Dec 27, 2022
Propel2 is an open-source high-performance Object-Relational Mapping (ORM) for modern PHP

Propel2 Propel2 is an open-source Object-Relational Mapping (ORM) for PHP. Requirements Propel uses the following Symfony Components: Config Console F

Propel 1.2k Dec 27, 2022
Ouzo Framework - PHP MVC ORM

Ouzo is a PHP MVC framework with built-in ORM and util libraries. PHP 8.0 or later is required. We believe in clean code and simplicity. We value unit

Ouzo 69 Dec 27, 2022
Simple Enum cast for Eloquent ORM using myclabs/php-enum.

Enum cast for Eloquent Simple Enum cast for Eloquent ORM using myclabs/php-enum. Requirements PHP 7.3 or higher Laravel 8.0 or higher Installation You

Orkhan Ahmadov 5 Apr 21, 2022
The Enobrev\ORM library is a small framework of classes meant to be used for simply mapping a mysql database to PHP classes, and for creating simply SQL statements using those classes.

The Enobrev\ORM library is a small framework of classes meant to be used for simply mapping a mysql database to PHP classes, and for creating simply SQL statements using those classes.

Mark Armendariz 0 Jan 7, 2022
Doctrine Object Relational Mapper (ORM)

3.0.x 2.9.x 2.8.x Doctrine 2 is an object-relational mapper (ORM) for PHP 7.1+ that provides transparent persistence for PHP objects. It sits on top o

Doctrine 9.5k Jan 2, 2023
Baum is an implementation of the Nested Set pattern for Laravel's Eloquent ORM.

Baum Baum is an implementation of the Nested Set pattern for Laravel 5's Eloquent ORM. For Laravel 4.2.x compatibility, check the 1.0.x branch branch

Estanislau Trepat 2.2k Jan 3, 2023
ORM layer that creates models, config and database on the fly

RedBeanPHP 5 RedBeanPHP is an easy to use ORM tool for PHP. Automatically creates tables and columns as you go No configuration, just fire and forget

Gabor de Mooij 2.2k Jan 9, 2023
A drop-in Doctrine ORM 2 implementation for Laravel 5+ and Lumen

Laravel Doctrine ORM A drop-in Doctrine ORM 2 implementation for Laravel 5+ $scientist = new Scientist( 'Albert', 'Einstein' ); $scientist->a

Laravel Doctrine 777 Dec 17, 2022
Extensions for the Eloquent ORM

Sofa/Eloquence Easy and flexible extensions for the Eloquent ORM. Currently available extensions: Searchable query - crazy-simple fulltext search thro

Jarek Tkaczyk 1.1k Dec 20, 2022
Builds Cycle ORM schemas from OpenAPI 3 component schemas

Phanua OpenAPI 3 + Jane + Cycle ORM = ?? Phanua builds Cycle ORM schemas from OpenAPI 3 component schemas. Released under the MIT License. WARNING: Th

Matthew Turland 5 Dec 26, 2022
Extra RedBean ORM

RedBeanPHP 5 RedBeanPHP is an easy to use ORM tool for PHP. Automatically creates tables and columns as you go No configuration, just fire and forget

GingTeam Development 5 Nov 23, 2022
Articulate - An alternative ORM for Laravel, making use of the data mapper pattern

Articulate Laravel: 8.* PHP: 8.* License: MIT Author: Ollie Read Author Homepage: https://ollie.codes Articulate is an alternative ORM for Laravel bas

Ollie Codes 4 Jan 4, 2022
MongoDB ORM that includes support for references,embed and multilevel inheritance.

Introduction Features Requirements Installation Setup Database Basic Usage - CRUD Relationship - Reference Relationship - Embed Collection Inheritance

Michael Gan 202 Nov 17, 2022
Low code , Zero Configuration ORM that creates models, config, database and tables on the fly.

?? ARCA ORM ?? Low code , Zero Configuration ORM that creates models, config, database and tables on the fly. ?? ???? Made in India ???? Complete docu

Scrawler Labs 28 Dec 18, 2022
Orm is a simple database abstraction layer that supports postgresql.

Orm What is it Orm is a simple database abstraction layer that supports postgresql. Welcome to join us or star us for encouragement. Requires php 8.1

null 2 Sep 28, 2022
TO DO LIST WITH LOGIN AND SIGN UP and LOGOUT using PHP and MySQL please do edit the _dbconnect.php before viewing the website.

TO-DO-LIST-WITH-LOGIN-AND-SIGN-UP TO DO LIST WITH LOGIN AND SIGN UP and LOGOUT using PHP and MySQL please do edit the _dbconnect.php before viewing th

Aniket Singh 2 Sep 28, 2021
The lightweight PHP database framework to accelerate development

The lightweight PHP database framework to accelerate development Features Lightweight - Less than 100 KB, portable with only one file Easy - Extremely

Angel Lai 4.6k Dec 28, 2022