Problem: What’s Going Wrong with Phalcon?
I ran into two particularly frustrating issues:
- The e_id Error: You write a query with a column like
note_id
, and Phalcon freaks out with a “Column ‘e_id’ doesn’t exist” error. - SoftDelete Hard Delete Woes: You’re using the
SoftDelete
behavior, but sometimes you need to permanently delete records. Phalcon’s model initialization and caching make this trickier than it should be.
Sound familiar? Let’s dive into why these issues are such a pain and how to fix them.
Agitate: Why These Problems Drive You Nuts
The e_id Error
Imagine you’re querying your NotesTranslations
model like this:
$translation = NotesTranslations::findFirst([
'conditions' => 'note_id = :noteId:',
'bind' => ['noteId' => 123]
]);
And then, BOOM! You get this error:Column 'e_id' doesn't belong to any of the selected models
.
What’s going on? Phalcon’s PHQL engine mistakenly parses note_id
as not e_id
, treating not
as an operator and e_id
as a column. Spoiler alert: there’s no e_id
in your database! You spend hours digging through stack traces, searching forums, and pulling your hair out because the solution is nowhere to be found. This error pops up whenever a column name contains something like not
, and it’s a total productivity killer.
The SoftDelete Problem
With SoftDelete
, you mark records as deleted (e.g., setting is_deleted = 1
) instead of removing them. But what if you need to actually delete a record—say, for a test environment or legal reasons? Phalcon’s modelsManager
initializes models and caches their metadata on the first query, so disabling SoftDelete
at runtime is nearly impossible. I tried everything:
- Adding a static
$disableSoftDelete
flag? Nope, the model’s already cached. - Creating a child class with an empty
initialize
? Phalcon still uses the parent class. - Writing raw PHQL queries? The model’s behaviors still kick in.
It’s like Phalcon is saying, “You chose soft delete, now live with it!” Meanwhile, your project deadlines are looming.
Solution: Here’s How to Fix It
1. Fixing the e_id Error
The fix for the e_id
error is simple but clever: wrap column names in square brackets ([]
) in PHQL queries. This tells Phalcon to treat the column name as a literal string. Here’s how:
$translation = NotesTranslations::findFirst([
'conditions' => '[note_id] = :noteId: AND [language_id] = :languageId:',
'bind' => [
'noteId' => 123,
'languageId' => 1
]
]);
This tiny change stops Phalcon from misinterpreting note_id
as not e_id
. No more errors, and your query works like a charm! Note: This only applies to PHQL queries. If you’re using raw SQL with PDO, you don’t need the brackets.
Pro Tip: If your column names include other tricky words like order
or set
, always use [column_name]
in PHQL to avoid similar issues.
2. Bypassing SoftDelete for Permanent Deletion
To permanently delete records while using SoftDelete
, the most reliable solution is to bypass Phalcon’s model layer entirely and use PDO with raw SQL. Why? Because PHQL queries trigger model initialization, which brings SoftDelete
back into play. Here’s how to do it:
Step 1: Raw SQL with PDO
Add a deletePermanently
method to your Notes
model:
public function deletePermanently()
{
$messages = [];
$db = $this->getDI()->getDb(); // Get PDO connection
try {
$sql = "DELETE FROM notes WHERE id = :id";
$stmt = $db->prepare($sql);
$stmt->execute(['id' => $this->id]);
error_log("Deleted {$stmt->rowCount()} rows from notes for id = {$this->id}");
return [true, $messages];
} catch (\PDOException $e) {
$messages[] = "Database error: {$e->getMessage()}";
error_log("Error in deletePermanently: {$e->getMessage()}");
return [false, $messages];
}
}
Step 2: Use It in the Controller
Call this method in your NotesController
:
public function deletePermanentlyAction()
{
$json = $this->request->getJsonRawBody();
if (!isset($json->noteId)) {
return $this->response->setJsonContent([
'status' => 'error',
'success' => false,
'error' => 'Note ID is required',
'data' => []
]);
}
$note = Notes::findFirst([
'conditions' => '[id] = :id:',
'bind' => ['id' => (int)$json->noteId]
]);
if (!$note) {
return $this->response->setJsonContent([
'status' => 'error',
'success' => false,
'error' => 'Note not found',
'data' => []
]);
}
try {
[$success, $messages] = $note->deletePermanently();
if (!$success) {
return $this->response->setJsonContent([
'status' => 'error',
'success' => false,
'error' => $messages ?: ['Failed to delete permanently'],
'data' => []
]);
}
} catch (\Exception $e) {
return $this->response->setJsonContent([
'status' => 'error',
'success' => false,
'error' => $e->getMessage(),
'data' => []
]);
}
return $this->response->setJsonContent([
'status' => 'success',
'success' => true,
'error' => null,
'data' => []
]);
}
Step 3: Debug Like a Pro
If the deletion fails, check for database constraints:
SHOW CREATE TABLE notes_translations;
If there’s a foreign key without ON DELETE CASCADE
, the code above handles it by deleting related records first.
Pro Tip: Log the number of affected rows ($stmt->rowCount()
) to confirm which tables are being modified. This makes debugging a breeze.
Final Thoughts
Phalcon is a beast of a framework, but these quirks can trip up even seasoned developers. Fixing the e_id
error with [note_id]
and bypassing SoftDelete
with PDO will save you hours of frustration. Hopefully, these solutions make your Phalcon projects smoother! If you hit another Phalcon roadblock, check my blog or drop me a line—we’ll figure it out together!