Doctrine Object Relational Mapper (ORM)

Related tags

Database orm
Overview
3.0.x 2.9.x 2.8.x
Build status Build status Build status
Coverage Status Coverage Status Coverage Status

Doctrine 2 is an object-relational mapper (ORM) for PHP 7.1+ that provides transparent persistence for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility without requiring unnecessary code duplication.

More resources:

Comments
  • Value objects (Based on #634)

    Value objects (Based on #634)

    This is PR #634 with the following additional changes:

    • Merged into master (fixed some conflict in Metadata classes)
    • Support for DQL queries on fields of embedded objects
    opened by schmittjoh 77
  • [WIP] Nullable embedded objects.

    [WIP] Nullable embedded objects.

    The idea was to have a simple (and clean) way to override the nullable property of embedded objects, so I've added the nullable attribute on the @Embedded annotation and it have 3 possible values:

    • NULL: The nullable option that was defined on the attributes of the embeddable class won't be overriden;
    • TRUE: All attributes of the embeddable class will be marked as nullable and the embeddable instance only would be created when data is not NULL;
    • ~~FALSE: All attributes of the embeddable class will be marked as non-nullable.~~

    There's a lot of things to be improved (mostly on UnitOfWork), but it's fully working with basic tests as you can see on ValueObjectsTest::testCRUDOfNullableEmbedded() case.

    I would appreciate a lot your opinions!

    WIP Delayed 
    opened by lcobucci 60
  • DDC-3480: ORM\Embeddable does not create ManyToOne column in the database

    DDC-3480: ORM\Embeddable does not create ManyToOne column in the database

    Jira issue originally created by user tvoslar:

    Following Embeddable

    /****
     * @ORM\Embeddable
     */
    class Address
    {
        /****
         * @ORM\ManyToOne(targetEntity="Country")
         */
        protected $country;
    

    won't save country attribute into the database table, other simple attributes (like type="text") saved normally.

        /****
         * @ORM\Embedded(class="LuciniLucini\Adsender\CommonBundle\Entity\Address")
         */
        private $address;
    
    Bug 
    opened by doctrinebot 56
  • Doctrine schema update command shows always few queries to be executed

    Doctrine schema update command shows always few queries to be executed

    I am using doctrine with symfony and run php bin/console doctrine:schema:update --force command. But it executes few queries always as following:

    ALTER TABLE company CHANGE current_subscription_id current_subscription_id INT DEFAULT NULL, CHANGE city city VARCHAR(255) DEFAULT NULL, CHANGE state state VARCHAR(255) DEFAULT NULL, CHANGE zip zip VARCHAR(255) DEFAULT NULL, CHANGE country country VARCHAR(255) DEFAULT NULL, CHANGE phone phone VARCHAR(255) DEFAULT NULL, CHANGE subscription_start_date subscription_start_date DATE DEFAULT NULL, CHANGE subscription_end_date subscription_end_date DATE DEFAULT NULL;

    Missing Tests Invalid 
    opened by jigarpancholi 50
  • TableDataGateway interface for fetching/saving entities without UnitOfWork interactions

    TableDataGateway interface for fetching/saving entities without UnitOfWork interactions

    While I was at SymfonyCon this year, I discussed something interesting with @lisachenko: providing a TableDataGateway-alike interface for the ORM.

    The current doctrine2 approach

    $entity = $someRepository->find(123);
    $entity->doSomething();
    $entityManager->flush();
    

    While this approach is very practical, it also exposes some serious memory/transactional/scope limitations, as the $entityManager leaks through different service layers.

    The proposed TableDataGateway approach

    $entity = $someRepository->find(123);
    $entity->doSomething();
    $someRepository->save($entity);
    

    The difference here is that the repository does not push the entity into any UnitOfWork: there are no flush() semantics, and calling $someRepository->save($entity); triggers an actual SQL query.

    Compatibility

    From what I can see, everything currently existing in the ORM could work exactly as it is working right now: just the hydration process would be simplified.

    • all entities are always in detached state
    • DQL just produces new objects, rather than searching in the UnitOfWork for existing instances
    • Proxies just lazy-load as before
    • persisters just work as before
    • everything is always treated as "dirty": saving a hydrated aggregate will cause INSERT/UPDATE queries for all the already initialized sub-graph (we may examine this case further - maybe one unit of work per sub-graph?)
    • mapping stays as-is
    • collections stay as-is

    Invariant changes

    • $someRepository->find(123) !== $someRepository->find(123);
    • race conditions between loading/saving objects must be fixed by using locking

    Advantages

    This sort of API has huge advantages when dealing with operations in:

    • batch processing (no more memory leaks there)
    • long running processes (memleaks gone, can reuse the same connection over multiple operations, without side-effects)
    • better handling of aggregates - different aggregates have different references and are incompatible
    • no more need to explicitly persist() an object before saving it

    Cleaning up persisters/hydrators

    In addition to all the above, this gives us the opportunity of examining scopes where the UnitOfWork leaked into, and to check whether we can get it out of there, and at what cost.

    New Feature Won't Fix 
    opened by Ocramius 48
  • [WIP] Value objects

    [WIP] Value objects

    This pull request takes a different approach than GH-265 to implement ValueObjects. Instead of changing most of the code in every layer, we just inline embedded object class metadata into an entities metadata and then use a reflection proxy that looks like "ReflectionProperty" to do the rewiring.

    The idea is inspired from Symfony Forms 'property_path' option, where you can write and read values to different parts of an object graph.

    This is a WIP, there have been no further tests made about the consequences of this approach. The implementation is up for discussion.

    opened by beberlei 47
  • Introduce `getIterable()` on AbstractQuery

    Introduce `getIterable()` on AbstractQuery

    I do not fancy accessing index 0 ([0]) while iterating iterable result from iterate() to get the entity.

    $results = $em->select('e')->from('entity')->getQuery()->iterate();
    foreach($resuls as $result) {
       $entityIsHere = $result[0];
    }
    

    So was thinking to introduce new method that gets rid of the index and returns the result same way as getResult() does. Only with the change that the new behaviour would keep row-by-row hydration.

    $generator = $em->select('e')->from('entity')->getQuery()->toIterable();
    foreach($generator as $result) {
       $entityIsHere = $result;
    }
    

    This change deprecates AbstractQuery::iterate and follow up PRs renamed the new method toIterable().

    • [x] Implement new row by row hydrating iterator
    • [x] Test the shapes are the same
    • [x] Add separate tests
    • [x] Find name for new method

    Follow ups:

    • https://github.com/doctrine/orm/pull/8268
    • https://github.com/doctrine/orm/pull/8293
    opened by simPod 46
  • DDC-3120: Warning: Erroneous data format for unserializing PHP5.6+

    DDC-3120: Warning: Erroneous data format for unserializing PHP5.6+

    Jira issue originally created by user techkey:

    Hi all,

    There seems to be something strange going on in the method newInstance() of the class \Doctrine\ORM\Mapping\ClassMetadataInfo.

    The original class method looks like this:

    {quote}\Doctrine\ORM\Mapping\ClassMetadataInfo#newInstance(){quote}

        {
            if ($this->_prototype === null) {
                $this->_prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->name), $this->name));
            }
    
            return clone $this->_prototype;
        }
    

    What happens now when a class that implements \Serializable is that a "Warning: Erroneous data format for unserializing" shows up and the function unserialize() returns false.

    That is because a class that implements \Serializable is expected to have the letter 'C' in the serialize string instead of the letter 'O'.

    I've made a quick work-around like this:

    {quote}\Doctrine\ORM\Mapping\ClassMetadataInfo#newInstance(){quote}

        {
            if ($this->_prototype === null) {
                $this->_prototype = @unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->name), $this->name));
                if ($this->_prototype === false) {
                    $this->_prototype = unserialize(sprintf('C:%d:"%s":0:{}', strlen($this->name), $this->name));
                }
            }
    
            return clone $this->_prototype;
        }
    

    That seems to work in my isolated tests and with Symfony2 and Doctrine2 and FOSUserBundle together.

    I've noticed this because the Model\User class from FOSUserBundle implements \Serializable.

    I had to implement a check in Model\User class because when using {{'C:%d:"%s":0:{}'}} the $serialized parameter of the unserialize method in the Model\User class is a empty string then.

    That warning seems only to happen with PHP5.6+. PHP5.5.12 and below doesn't show that warning.

    I hope someone can shine some light on this, thank you,

    Cornelis.

    Bug 
    opened by doctrinebot 46
  • DDC-93: It would be nice if we could have support for ValueObjects

    DDC-93: It would be nice if we could have support for ValueObjects

    Jira issue originally created by user ablock:

    class User {
        /****
         * @Column(type="string")
         */
        private $address;
    
        /****
         * @Column(type="string")
         */
        private $city;
    
        /****
         * @Column(type="string")
         */
        private $state;
    }
    

    We could have:

    class User {
        /****
         * @Component(class="Address")
         */
         private $address;
    }
    

    It would my life a lot easier....


    Notes for implementation

    Value objects can come in two forms:

    a) as embedded value objects b) as collections of value objects

    An implementation should concentrate on a) first. The following things all concentrate on a).

    DQL Support

    Conditions:

    1. "select f from Foo f where f.embedded.value = ?1" (setParameter(1, $scalarValue))
    2. "select f from Foo f where f.embedded = ?1" (setParameter(1, $embeddedValueObject))

    At least Nr.1 must be possible in a first implementation.

    Selecting:

    1. "select f from Foo f" must explode embedded value objects in the SQL SELECT clause.
    2. "select f.embedded from Foo f" must explode the columns of the embedded object in the SQL SELECT clause.

    At least Nr. 1 must be possible in a first implementation, obviously.

    Components affected (among others): Parser, SqlWalker, ...

    Persisters

    The persisters need to take embedded value objects into account when persisting as well as loading entities.

    Components affected (among others): Persisters, UnitOfWork, ...

    Metadata

    ClassMetadataInfo needs to be extended with a field (probably an array) that contains the mappings of embedded values. New annotations as well as XML/YAML elements are needed.

    Components affected (among others): ClassMetadataInfo, AnnotationDriver, YamlDriver, XmlDriver, doctrine-mapping.xsd, ...

    Change Tracking

    If value objects are supposed to be immutable this is easy and might require no or few changes. If, however, we want to track changes in mutable value objects it might get more complicated.

    Components affected (among others): UnitOfWork, ...

    opened by doctrinebot 45
  • Efficient counting on Criteria

    Efficient counting on Criteria

    Hi,

    This is an attempt to solve this very annoying issue: http://www.doctrine-project.org/jira/browse/DDC-2217

    I'm not sure about my solution as I'm not really into Doctrine internals.

    Use case

    I have a library that is based exclusively on Criteria API. A Paginator adapter uses an empty Criteria to count how many elements are in the collection. However, EntityRepository's matching method always initialize the whole collection into an ArrayCollection. This is unusable in most cases and make the API unusable for my use case.

    Solution

    I've created a new LazyCollectionCriteria that implements an efficient count. I think the cleanest solution would be to add a method to the Selectable interface: with a count method. But I think this is not possible now, unfortunately.

    If the solution seems nice to you, I'll write the tests.

    ping @ocramius, @macnibblet and @thinkscape (this should make ZfrRest usable :D)

    opened by bakura10 45
  • [DDC-357] Effective toOne joins

    [DDC-357] Effective toOne joins

    What is this?

    I've kind of rewriten querying of toOne relations. It is more data and query-count effective.

    How?

    Let's demonstrate it on CmsUser and CmsAddress from tests models. Let's solve behaviour for toOne relations that are not mentioned in the query.

    SELECT u FROM CmsUser u
    

    lazy + mapped by side

    Already implemented, result is that CmsAddress would be proxy.

    lazy + inverse side

    CmsUser has CmsAddress relation that is mapped and owned by CmsAddress entity.

    What has to happen? The identifier of CmsAddress cannot be loaded from users table nad has to be added automatic join for the table. Because it's lazy it will be hydrated as proxy, becase that is exactly what I've asked for.

    If it would have been eagerly loaded, It would create 1+N queries problem that I'm trying to avoid with this. I have the relation as lazy, if I knew I would have needed it and wanned to optimized, I'd join it, but I didn't.

    Result is therefore CmsUser entity + CmsAddress proxy

    eager - both inverse and mapped by sides

    The appropriate query component is generated with autojoin and auto selecting of the entity.

    If it is self-referencing, the auto join is not generated becase it would cause infinite recursion.

    Why?

    I've given this a lot of thought and tested it on our not-so-small application. We have unfortunately lot of entitiy relations that are mapped on the inverse side than we need to select, which is effectively killing performace DDC-357

    I would have to go and list all the entities as partials to save performace creating such monsters as this

    $builder = $repository->createQueryBuilder("o")
        ->leftJoin("o.voucher", "vu")->addSelect("partial vu.{id}")
        ->leftJoin("o.address", "a")->addSelect("a")
        ->leftJoin("o.restaurant", "r")->addSelect("partial r.{id, name}")
        ->leftJoin("o.payment", "p")->addSelect("partial p.{id}")
        ->leftJoin("o.rating", "rat")->addSelect("partial rat.{id}")
        ->leftJoin("r.settings", "rs")->addSelect("partial rs.{id}")
        ->leftJoin("r.address", "ra")->addSelect("ra")
        ->leftJoin("r.position", "rp")->addSelect("partial rp.{id}");
    # plus about five more just to make save performace
    

    We all know that hydrating a large result set is a bottleneck and if I say the relation is lazy and I'm not joining it I really don't want it to be joined with all it's data!

    Now imagine I just want to select few orders and render some data on the page.. I have tens of queries like this just because I have to. This is wrong that the ORM is tripping my feet like this.

    What now?

    I know I have to solve theese:

    • [ ] more refactoring?
    • [ ] more tests
    • [ ] what to do with issue tests that now have changed behaviour?

    Any suggestions? Let's have a reasonable discussion, please don't just close this, I've put a lot of effort into this.

    opened by fprochazka 44
  • Order of referenced child deletions not correct when referencing to itself

    Order of referenced child deletions not correct when referencing to itself

    Bug Report

    | Q | A |------------ | ------ | BC Break | no | Version | 2.14.0

    Summary

    The order in which the UoW wants to delete children is somehow missing a dependency when there is an association to the same class even if you specified cascade: ['remove']. Given you have

    • Parent -> has an association to children with cascade: ['remove']
    • Child 1
    • Child 2 -> has an association to Child 1 with cascade: ['remove'].

    Now you delete the parent. Doctrine tries to remove Child 1 first instead of Child 2 causing a foreign constraint violation:

    Uncaught PDOException: SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (test.child_entities, CONSTRAINT FK_DAEB194156A273CC FOREIGN KEY (origin_id) REFERENCES child_entities (id))

    How to reproduce

    I've created a test setup because I'm not familiar with the internals of ORM at all. So here we go:

    1. Clone https://github.com/Toflar/doctrine-orm-bug-reproducer
    2. Run composer install
    3. Adjust dummy mysql connection example in bootstrap.php.
    4. Run bin/doctrine orm:schema-tool:drop --force && bin/doctrine orm:schema-tool:create
    5. Run php test.php.

    Expected behavior

    It should not throw an exception. Doctrine should remove Child 2 first.

    opened by Toflar 0
  • Add Fully-Qualified class name in UnrecognizedField exception

    Add Fully-Qualified class name in UnrecognizedField exception

    Currently if we use a field that is not properly registered in our mapping, we end up with the following exception from the BasicEntityPersister:

    Unrecognized field: {field}
    

    As with a complete stack trace we may be able to dig enough to find the right mapping, it can take more time to find the concerned entity in a context with less informations, such as logs.

    This PR adds the entity FQCN to the exception message to ease debugging in such cases, using the class metadata available in BasicEntityPersister::class property. We end up with:

    Unrecognized field: {fqcn}::{field}
    

    As I didn't encountered any tests for the existing UnrecognizedException class behavior, I didn't add some but I can if needed.

    opened by Kern046 4
  • Clearing the em before accessing an uninitialized relation results in

    Clearing the em before accessing an uninitialized relation results in "typed property must not be accessed before initialization..." error

    BC Break Report

    | Q | A |------------ | ------ | BC Break | yes | Version | 2.14.0

    Summary

    Consider the following code:

    $results = $repo->createQueryBuilder('e')
        ->innerJoin('e.relation', 'r')
        ->getQuery()
        ->execute()
    ;
    
    $em->clear();
    
    echo $results[0]->relation->value;
    

    Previous behavior

    In 2.13.5, would echo the relation's value property.

    Current behavior

    In 2.14.0:

    Error: Typed property Relation::$value must not be accessed before initialization

    @nicolas-grekas, could this be related to #10187?

    Reproducer: #10337

    opened by kbond 4
  • ObjectHydrator::getEntityFromIdentityMap() does not work with backed enum

    ObjectHydrator::getEntityFromIdentityMap() does not work with backed enum

    Bug Report

    | Q | A |------------ | ------ | BC Break | no | Version | 2.14.0

    Summary

    I have an entity with a backed enum as primary key. When I fetch a fresh entity from the database, everything works fine. But if I try to fetch an entity that is already known in the identity map, it fails.

    Current behavior

    It cannot load an entity with backed enum as primary key from the ObjectHydrator identity map.

    Error "Object of class MyEnum could not be converted to string"

    From ObjectHydrator on line 292

    How to reproduce

    We need 2 entities, with ManyToOne relation, so we can use a join in our request.

    enum MyEnum: string
    {
        case Yellow = 'yellow';
        case Blue = 'blue';
    }
    
    #[ORM\Entity, ORM\Table(name: 'foo')]
    class Foo
    {
        #[ORM\Id, ORM\ManyToOne(inversedBy: 'foos'), ORM\JoinColumn(nullable: false)]
        private readonly FooCollection $collection;
    
        #[ORM\Id, ORM\Column]
        private readonly MyEnum $myEnum;
    }
    
    #[ORM\Entity, ORM\Table(name: 'foo_collection')]
    class FooCollection
    {
        #[ORM\Id, ORM\Column]
        private int $id;
    
        /** @var Collection<int, Foo> */
        #[ORM\OneToMany(targetEntity: Foo::class, mappedBy: 'collection')]
        private Collection $foos;
    }
    
    create table foo_collection
    (
        id int not null primary key
    );
    
    create table foo
    (
        collectionId int          not null,
        myEnum       varchar(255) not null,
        primary key (collectionId, myEnum),
        constraint foo_collection_id_fk
            foreign key (collectionId) references foo_collection (id)
    );
    
    INSERT INTO foo_collection (id) VALUES (1);
    INSERT INTO foo (collectionId, myEnum) VALUES (1, 'yellow'), (1, 'blue');
    

    And then, just run 2 queries to trigger the loading from the identity map

        public function test(EntityManagerInterface $entityManager): void
        {
            $entityManager
                ->getRepository(FooCollection::class)
                ->createQueryBuilder('collection')
                ->leftJoin('collection.foos', 'foo')->addSelect('foo')
                ->getQuery()
                ->getResult();
    
            $entityManager
                ->getRepository(FooCollection::class)
                ->createQueryBuilder('collection')
                ->leftJoin('collection.foos', 'foo')->addSelect('foo')
                ->getQuery()
                ->getResult();
        }
    

    Expected behavior

    No error

    opened by noemi-salaun 0
Releases(2.14.0)
Owner
Doctrine
The Doctrine Project is the home to several PHP libraries primarily focused on database storage and object mapping.
Doctrine
[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
Convention-based Object-Relational Mapper

Corma Corma is a high-performance, convention-based ORM based on Doctrine DBAL. Corma is great because: No complex and difficult to verify annotations

Michael O'Connell 30 Dec 20, 2022
Analogue ORM : Data Mapper ORM for Laravel/PHP

(this project is looking for a new maintainer) Analogue ORM Analogue is a flexible, easy-to-use ORM for PHP. It is a transposition of the Eloquent ORM

Analogue ORM 632 Dec 13, 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
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
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
A data mapper implementation for your persistence model in PHP.

Atlas.Orm Atlas is a data mapper implementation for persistence models (not domain models). As such, Atlas uses the term "record" to indicate that its

null 427 Dec 30, 2022
A simple PHP library to transfer data from a source (object or array) to an object.

SimplexMapper A simple PHP library to transfer data from a source (object or array) to an object. $dbData = [ 'username' => 'pfazzi', 'emailAd

Patrick Luca Fazzi 4 Sep 22, 2022
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
PHP DataMapper, ORM

Cycle ORM Cycle is PHP DataMapper, ORM and Data Modelling engine designed to safely work in classic and daemonized PHP applications (like RoadRunner).

Cycle ORM 1.1k Jan 8, 2023
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
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
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
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
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
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
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