Troubleshooting

What to do when things don't seem to work as you would expect.

General Debugging

For debugging purposes, it is often helpful to see the actual SQL that was executed. There is a helper method on the repository class (getSql) that can help with this, by returning the last SQL query executed (note: this only works in development mode, unless you set the recordQueries config option to true, in which case it will also work in production mode - not recommended, as it uses more memory). This will be returned as a string with all of the parameters resolved as values, so you can just copy and paste it into a MySQL GUI without having to tediously replace all the prepared statement tokens first. Note: Objectiphy will always execute queries using prepared statements - to see the exact prepared statement that was executed, call getSql(false).

Often, a repository will be used to execute more than one query, and there might be additional information Objectiphy can provide about how it processed your request. You can get the full details using the getExplanation method. This returns an Explanation object, which includes the full history of all queries executed by the repository since it was created.

Property is converted to a string

If you build a query and reference a property but when you examine the SQL that is generated (using the getSql() method as described above), you find it has just wrapped the property name in quotes (treating it as a literal string) rather than converted it to a table/column name, this indicates that Objectiphy was unable to recognise the property name. Usually this means you have simply mis-typed the property name. Remember that with the query builder you must use the property name from your entity, NOT the column name from the database (if you must use a column name, wrap it in backticks ` but generally that is best avoided).

If you need to refer to a property on a joined entity, use the alias from the join followed by a dot, followed by the property name. If the property relates to a child object, and you want to use the ID of the child object, you can typically just use the child object property name without needing to drill down further into the child. For example if you have an object of type Order with a property named 'customer', you can do this:

$queryBuilder = QB::Create()
    ->from(Contact::class)
    ->leftJoin(Order::class, 'o')
    ->on('o.customer', '=', 'customerId')

Note that in the above example, if you had put o.customerId instead of o.customer but the property name on the Order entity is customer (not customerId), it will be treated as a literal string, which is not what you want. Also, in the above example, customerId is a property on the parent entity (in this case Contact). Again, this must be an actual property name, otherwise it will be treated as a literal string.

Child object returned as null

If you are using serialization groups, and you find that a child object which you expect to be hydrated returns null, check to make sure that at least one property on the child object has a serialization group that is being used for the query. If Objectiphy tries to load an object using late binding, but there are no properties to fetch, it cannot return anything, even if there is a child record present, so this will result in the child object being set to null. Also bear in mind that (for performance reasons) if there are PHP attributes present for mapping definitions, any old-style annotations in that class will be ignored - so any Symfony serialization groups that use old annotations will not be recognised by Objectiphy in that case (see attributes).

Objectiphy attempts to update instead of insert

When you ask Objectiphy to save an entity, it checks to see whether or not a primary key value is present. If there is a value for the primary key, it will attempt to update, otherwise, it will attempt to insert. This accounts for the most common use case of having an auto-incrementing primary key in your database. However, if you are creating your own primary key values, you have to let Objectiphy know that it should not rely on auto-increment by setting the Column mapping properties as follows: primaryKey=true, autoIncrement=false.

Objectiphy will then attempt to insert the record, and if the primary key value already exists, it will do an update (using ON DUPLICATE KEY UPDATE). Alternatively, you can set the third parameter of the saveEntity method to true, which will have the same effect.

Objectiphy will not delete a relationship even though I set the child to null

This can happen if you have a parent/child relationship where the child owns the relationship - ie. the column in the database that holds the foreign key is on the child table. In that case, to remove the relationship, you must delete the parent from the child and save the child entity. Saving the parent entity will not remove the relationship, even if you delete the parent from the child (although it might appear to because the parent entity you pass to it will be cached in-process so you won't see the child pop back up until the next request). For example, suppose you have the following entities:

namespace MyNamespace;

use Objectiphy\Objectiphy\Mapping\Relationship;

class Parent
{
    #[Relationship(
        relationshipType: 'one_to_one',
        childClassName: Child::class,
        mappedBy: 'parent'
    )] 
    protected Child $child;
}
namespace MyNamespace;

use Objectiphy\Objectiphy\Mapping\Relationship;

class Child
{
    #[Relationship(
        relationshipType: 'one_to_one',
        childClassName: Parent::class,
        sourceJoinColumn: 'parent_id'
    )]
    protected Parent $parent;
}

Here we have a bi-directional relationship, with the parent referencing the child and the child referencing the parent, but the property with the actual column mapping information is on the child class - so the child owns the relationship. Now if we have an instance of a parent object, and we want to remove its relationship to the child, we must remove the parent from the child and save the child. So...

//This is wrong...
$parent = $repository->find(123);
$parent->child = null;
$repository->saveEntity($parent);
$repository->clearCache(); //Force a refresh from the database, not cache
$refreshedParent = $repository->find(123);
$result = $refreshedParent->child === null; //$result is FALSE!

//This is also wrong...
$parent = $repository->find(123);
$parent->child->parent = null;
$repository->saveEntity($parent);
$repository->clearCache(); //Force a refresh from the database, not cache
$refreshedParent = $repository->find(123);
$result = $refreshedParent->child === null; //$result is FALSE!
$result2 = $refreshedParent->child->parent === null; //$result2 is also FALSE!

//This is correct...
$parent = $repository->find(123);
$parent->child->parent = null;
$repository->saveEntity($parent->child);
$repository->clearCache(); //Force a refresh from the database, not cache
$refreshedParent = $repository->find(123);
$result = $refreshedParent->child === null; //$result is TRUE!

Note that in the above code sample, we make a call to clearCache before attempting to re-load the entity from the database. This is because by default, Objectiphy will save time by returning the object you asked it to save, and on that object, the child will be null (because you set it to null), even though in the database the relationship still exists.

You might be wondering why it doesn't just see that the relationship has been removed and delete it in the database. The reason is that you are asking Objectiphy to save the parent object, so it knows who the parent is. As a safety feature, if any children do not have a reference to their parent, Objectiphy helpfully adds it, to ensure children don't go missing when dealing with partially hydrated objects. If you save the child object directly though, Objectiphy does not know which object is the parent, so will not add the parent back on.

Objectiphy uses too much memory

If you need to run a lot of queries, or very large queries, or have large and complicated hierarchies of objects, memory consumption can become a problem. You can free up resources used by Objectiphy by calling the clearCache and/or the clearQueryHistory methods on your repository (see the caching page for more information). Also consider whether it would be better to use fetch on demand or pagination to avoid loading large numbers of records into memory in one go. See the optimisation page for further tips.

Entities in session become __PHP_Incomplete_Class

If you store entities in session, when you try to load them up again on the next call, you may find that PHP is unable to create the object due to an incomplete class. This is because proxy objects are created on the fly from class definition files that will not be picked up by your autoloader. If you are planning to store entities in session then, you must ensure that you eager load all relationships, so that you don't get a proxy back from Objectiphy. For example:

$repository->setConfigOption(ConfigOptions::EAGER_LOAD_TO_ONE, true);
$repository->setConfigOption(ConfigOptions::EAGER_LOAD_TO_MANY, true);

Last updated