1. Go to this page and download the library: Download pierresh/phpstan-pdo-mysql library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
// ✅ Valid SQL
$stmt = $db->prepare("SELECT id, name FROM users WHERE id = :id");
// ❌ Missing parameter
$stmt = $db->prepare("SELECT * FROM users WHERE id = :id AND name = :name");
$stmt->execute(['id' => 1]); // Missing :name
// ❌ Extra parameter
$stmt = $db->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => 1, 'extra' => 'unused']);
// ❌ Wrong parameter name
$stmt = $db->prepare("SELECT * FROM users WHERE id = :user_id");
$stmt->execute(['id' => 1]); // Should be :user_id
// ✅ Valid bindings
$stmt = $db->prepare("SELECT * FROM users WHERE id = :id AND name = :name");
$stmt->execute(['id' => 1, 'name' => 'John']);
$stmt = $db->prepare("SELECT * FROM users WHERE id = :id");
$stmt->bindValue(':id', 1); // This is ignored!
$stmt->execute(['name' => 'John']); // Wrong parameter
// ❌ Column typo: "nam" instead of "name"
$stmt = $db->prepare("SELECT id, nam, email FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);
/** @var object{id: int, name: string, email: string} */
$user = $stmt->fetch();
// ❌ Missing column
$stmt = $db->prepare("SELECT id, name FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);
/** @var object{id: int, name: string, email: string} */
$user = $stmt->fetch();
// ✅ Valid columns
$stmt = $db->prepare("SELECT id, name, email FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);
/** @var object{id: int, name: string, email: string} */
$user = $stmt->fetch();
// ✅ Also valid - selecting extra columns is fine
$stmt = $db->prepare("SELECT id, name, email, created_at FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);
/** @var object{id: int, name: string, email: string} */
$user = $stmt->fetch(); // No error - extra column `created_at` is ignored
/**
* @phpstan-type User object{id: int, name: string, email: string}
*/
class UserRepository
{
public function findUser(int $id): void
{
// Typo: "nam" instead of "name", also missing "email"
$stmt = $this->db->prepare("SELECT id, nam FROM users WHERE id = :id");
$stmt->execute(['id' => $id]);
/** @var User */
$user = $stmt->fetch();
}
}
// ❌ fetchAll() returns an array of objects, not a single object
$stmt = $db->prepare("SELECT id, name FROM users");
$stmt->execute();
/** @var object{id: int, name: string} */
$users = $stmt->fetchAll(); // Wrong: should be array type
// ❌ fetch() returns a single object, not an array
$stmt = $db->prepare("SELECT id, name FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);
/** @var array<object{id: int, name: string}> */
$user = $stmt->fetch(); // Wrong: should be single object type
// ✅ Correct: fetchAll() with array type (generic syntax)
$stmt = $db->prepare("SELECT id, name FROM users");
$stmt->execute();
/** @var array<object{id: int, name: string}> */
$users = $stmt->fetchAll();
// ✅ Correct: fetchAll() with array type (suffix syntax)
/** @var object{id: int, name: string}[] */
$users = $stmt->fetchAll();
// ✅ Correct: fetch() with single object type
$stmt = $db->prepare("SELECT id, name FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);
/** @var object{id: int, name: string} */
$user = $stmt->fetch();
// ❌ Missing |false in type annotation
$stmt = $db->prepare("SELECT id, name FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);
/** @var object{id: int, name: string} */
$user = $stmt->fetch(); // Can return false!
// ✅ Correct: Include |false in union type
$stmt = $db->prepare("SELECT id, name FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);
/** @var object{id: int, name: string}|false */
$user = $stmt->fetch();
// Both styles are supported:
/** @var object{id: int, name: string} | false */ // With spaces
/** @var false|object{id: int, name: string} */ // Reverse order
// ✅ Correct: Check rowCount() with throw/return
$stmt = $db->prepare("SELECT id, name FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);
if ($stmt->rowCount() === 0) {
throw new \RuntimeException('User not found');
}
/** @var object{id: int, name: string} */
$user = $stmt->fetch(); // Safe - won't execute if no rows
// ✅ Correct: Check for false after fetch
$stmt = $db->prepare("SELECT id, name FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);
/** @var object{id: int, name: string} */
$user = $stmt->fetch();
if ($user === false) {
throw new \RuntimeException('User not found');
}
// Or: if ($user !== false) { ... }
// Or: if (!$user) { ... }
// ❌ rowCount() without throw/return doesn't help
$stmt = $db->prepare("SELECT id, name FROM users WHERE id = :id");
$stmt->execute(['id' => 1]);
if ($stmt->rowCount() === 0) {
// Empty block - execution continues!
}
/** @var object{id: int, name: string} */
$user = $stmt->fetch(); // Still can return false!
// ❌ Self-reference in JOIN condition
$stmt = $db->prepare("
SELECT *
FROM orders
INNER JOIN users ON users.id = users.id
");
// ❌ Self-reference in WHERE clause
$stmt = $db->prepare("
SELECT *
FROM products
WHERE products.category_id = products.category_id
");
// ❌ Multiple self-references in same query
$stmt = $db->prepare("
SELECT *
FROM orders
INNER JOIN products ON products.id = products.id
WHERE products.active = products.active
");
// ✅ Valid JOIN - different columns
$stmt = $db->prepare("
SELECT *
FROM orders
INNER JOIN users ON orders.user_id = users.id
");
// ✅ Valid WHERE - comparing to a value
$stmt = $db->prepare("
SELECT *
FROM products
WHERE products.category_id = 5
");
// ❌ Table 'user' doesn't exist - should be 'users'
$stmt = $db->prepare("SELECT user.name FROM users WHERE users.id = :id");
// ❌ Wrong alias - using 'usr' but alias is 'u'
$stmt = $db->prepare("SELECT usr.name FROM users AS u WHERE u.id = :id");
// ❌ Table 'orders' not in FROM or JOIN
$stmt = $db->prepare("SELECT users.id, orders.total FROM users WHERE users.id = :id");
// ✅ Correct table name
$stmt = $db->prepare("SELECT users.name FROM users WHERE users.id = :id");
// ✅ Correct alias usage
$stmt = $db->prepare("SELECT u.name FROM users AS u WHERE u.id = :id");
// ✅ Both table name and alias can be used
$stmt = $db->prepare("SELECT users.id, u.name FROM users AS u WHERE u.id = :id");
// ✅ Multiple tables with JOIN
$stmt = $db->prepare("
SELECT u.name, o.total
FROM users AS u
INNER JOIN orders AS o ON u.id = o.user_id
WHERE u.id = :id
");
// ❌ Always-true condition
$stmt = $db->prepare("
SELECT *
FROM users
WHERE 1 = 1
");
// ❌ Always-false condition
$stmt = $db->prepare("
SELECT *
FROM users
WHERE 1 = 0
");
// ❌ String literal tautology
$stmt = $db->prepare("SELECT * FROM users WHERE 'yes' = 'yes'");
// ❌ Boolean tautology
$stmt = $db->prepare("SELECT * FROM users WHERE TRUE = FALSE");
// ❌ Tautology in JOIN condition
$stmt = $db->prepare("
SELECT *
FROM users
INNER JOIN orders ON 1 = 1
");
// ✅ Valid - comparing column to literal
$stmt = $db->prepare("SELECT * FROM users WHERE status = 1");
// ✅ Valid - using parameter
$stmt = $db->prepare("SELECT * FROM users WHERE id = :id");
// ❌ IFNULL is MySQL-specific
$stmt = $db->prepare("SELECT IFNULL(name, 'Unknown') FROM users");
// ❌ IF() is MySQL-specific
$stmt = $db->prepare("SELECT IF(status = 1, 'Active', 'Inactive') FROM users");
// ✅ COALESCE is portable (works in MySQL, PostgreSQL, SQL Server)
$stmt = $db->prepare("SELECT COALESCE(name, 'Unknown') FROM users");
// ✅ CASE WHEN is portable
$stmt = $db->prepare("SELECT CASE WHEN status = 1 THEN 'Active' ELSE 'Inactive' END FROM users");
// ❌ NOW() is MySQL-specific
$stmt = $db->prepare("SELECT * FROM users WHERE created_at > NOW()");
// ❌ CURDATE() is MySQL-specific
$stmt = $db->prepare("SELECT * FROM users WHERE birth_date = CURDATE()");
// ❌ LIMIT offset, count is MySQL-specific
$stmt = $db->prepare("SELECT * FROM users LIMIT 10, 5");
// ✅ Bind PHP datetime variables
$stmt = $db->prepare("SELECT * FROM users WHERE created_at > :now");
$stmt->execute(['now' => (new \DateTime())->format('Y-m-d H:i:s')]);
$stmt = $db->prepare("SELECT * FROM users WHERE birth_date = :today");
$stmt->execute(['today' => (new \DateTime())->format('Y-m-d')]);
// ✅ LIMIT count OFFSET offset is portable
$stmt = $db->prepare("SELECT * FROM users LIMIT 5 OFFSET 10");
use PHPUnit\Framework\TestCase;
class MyTest extends TestCase
{
public function testExample(): void
{
$row = $stmt->fetch(); // Fetch data from database
ddt($row); // Dumps type and stops execution
}
}
use PHPUnit\Framework\TestCase;
class MyTest extends TestCase
{
public function testExample(): void
{
$row = $stmt->fetchObject(); // Fetch data from database
ddc($row); // Dumps class definition and stops execution
}
}
class Item
{
public int $id;
public string $name;
public string $email;
public ?string $phone;
}
// 1. First, discover the structure using ddc()
$stmt = $db->query("SELECT id, name, email, phone FROM users WHERE id = 1");
$row = $stmt->fetchObject();
ddc($row);
// 2. Create your view model class from the output
class UserViewModel
{
public int $id;
public string $name;
public string $email;
public ?string $phone;
}
// 3. Use it with PDO::fetchObject()
$stmt = $db->query("SELECT id, name, email, phone FROM users WHERE id = 1");
$user = $stmt->fetchObject(UserViewModel::class);
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.