mothership-ec / cog

Cog
Other
2 stars 3 forks source link

Lazy Loading #379

Closed irisSchaffer closed 10 years ago

irisSchaffer commented 10 years ago

Options

Our options are basically to build our own lazy-loading strategy or instead use a whole ORM to take care of all database operations. I have looked into a few ORMs, but it seems like using one is a lot more involved than writing our own little lazy loading thing. At the same time using an ORM could save us a lot of time in the future, as it makes filtering and sorting really easy, can help with migrations and offers lazy loading out of the box.

ORMs

Eloquent

Usage

class User extends Eloquent {
    protected $table = 'table_name';
    protected $guarded = ['*']; // no mass assignment

    public $firstName;
    // ...
}

API

$user = new User;
$user->firstName = 'John';
$user->save();

$user = User::find(1);
$users = User::where('firstName', '=', 'John')->get();
$allUsers = User::all();
$allUsers->orderBy('firstName');

Propel

Usage

<database name="user" defaultIdMethod="native">
  <table name="user" phpName="User">
    <!-- column and foreign key definitions go here -->
  </table>
</database>

You then run commands and Propel creates a base model (with all the getters/setters etc.) that you can override with your actual model, many other things and also the UserQuery class.

API

$user = new User;
$user->setFirstName('John');
$user->save();

$q = new UserQuery();
$user = $q->findPK(1);
$users = $q
    ->filterByFirstName('John')
    ->find();
$allUsers = $q
    ->orderByFirstName()
    ->find();

Doctrine

Usage

<doctrine-mapping>
  <entity name="User">
    <field name="id" type="integer" />
    <field name="firstName" length="255" />
  </entity>
</doctrine-mapping>

OR

/** @Entity */
class User
{
    /** @Column(type="integer") */
    private $id;
    /** @Column(length=140) */
    private $firstName;
}

OR

User:
  type: entity
  fields:
    id:
      type: integer
    firstName:
      length: 140

After that, again, you run a command and it creates your model that you can then work with directly.

API

$entityManager = $this->get('doctrine.entity-manager');
$user = new User;
$user->setFirstName('John');
$entityManager->persist($user);
// other write operations
$entityManager->flush(); // commits transaction

$repo = $entityManager->getRepository('Cog\User\User');
$user = $repo->find(1);
$users = $repo->findByFirstName('John'); // or ->findBy(['firstName' => 'John']);
$allUsers->$repo->findBy([], ['firstName' => 'ASC']);
thomasjthomasj commented 10 years ago

Could we use something like https://github.com/illuminate/database ? I only had a quick skim but it looks like it's basically the Laravel implementation taken out of the framework. I'm still not sure how exactly our implementation would work, but I'm reluctant to do anything that will dictate how we build our models in the way that Doctrine does. Also time is a pretty big factor so it needs to be something that can be implemented bit by bit and ideally won't be something that breaks BC

irisSchaffer commented 10 years ago

Well, to be honest, Doctrine doesn't really dictate the way you build models, it's just you can use it to generate getters and setters etc, but that's totally up to you, so you can still have public member variables (although, to be honest, I love always having getters and setters, instead of sometimes having them for some things, but then not for others, bla bla bla). Eloquent does dictate the way we build our models though: You have to extend Eloquent in all your models, which I really don't like... They also mix up db-logic and model-logic in the model class, where doctrine and propel have an additional layer of dealing with the db-logic. Propel seems to be a bit weird because it creates a base class for you and a class that you can/should edit on top of that, so you actually have to create their model generation tool, if I understand the docs right.

To be clear: All of these options would be a fairly big BC break if we want to make use of their full potential. However, we could probably phase them in, as we do/did with forms. One thing that we have to keep in mind are transactions and transactional events.

To be absolutely honest with you, I think these options are great, but it might not be the right time to switch to one of them. It would be a major refactor and BC break and very time consuming. One thing we can still look into is using only the query builders of these systems but not the ORMs. So we could still use Eloquent's or Doctrine's query builder, to simplify our db decorators, but then we would lose the big advantage of automatically mapping the db to the models...

thomasjthomasj commented 10 years ago

I think phasing in is probably okay, I'm just worried about how much this could potentially restructure things.

I'm a little reluctant to use Doctrine generally just because when I used it before I found it to be super fussy and difficult to debug.

I like getters and setters too, do we know if Symfony's bind() functionality works with them as well as with public properties?

irisSchaffer commented 10 years ago

Do you mean the form binding functionality? Yes, that's how it generally works in Symfony (they use Doctrine, too). Symfony can access public variables ($foo) but also automatically checks all methods with names like getFoo, setFoo and isFoo and addFoos and removeFoos for arrays/collections. I think they even have the option of using a reflection class and accessing private/protected properties, if they can't use any of the methods.

thomasjthomasj commented 10 years ago

Oh my bad, it's not actually in Symfony, it's in our own DB\Result class (https://github.com/messagedigital/cog/blob/develop/src/DB/Result.php). If we were to move towards using getters and setters we would need to adapt that class to allow it to look for them

irisSchaffer commented 10 years ago

Oh, now I see what you mean! Yeah, we if we wanted to use setters and getters more widely, we would have to change that. We could actually use Symfony's Property Accessor (we already depend on that because that's how the form component accesses properties), which would automatically check all the setters, getters, is-ers etc.

irisSchaffer commented 10 years ago

Just to add this in here: We have decided for now to implement our own lazy loading mechanisms, but at some point switch to one of the ORMs. I am going to close this issue but add the part about the ORMs to the existing ORM issue that Laurence raised: #146