Download the PHP package migears/mitable without Composer

On this page you can find all versions of the php package migears/mitable. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package mitable

migears/mitable

Version

Minimalist single-table execution helper for one-off data migrations — DDL, CRUD, schema introspection, and cursor-based iteration, with one class per SQL dialect.

A table class wraps a PDO connection around a single table. Point it at one table, reshape the schema, move and transform rows, then get out of the way. No query builder dependency — just pure PDO.

Background: miGears is the open-source successor of TinyGears, a self-developed PHP framework. It was renamed and open-sourced recently because the name TinyGears is already taken in the open-source community.

Structure

Piece Role
MiTableInterface The contract: everything a table implementation must do
TableOperations The dialect-agnostic half — CRUD, condition compilation, cursor iteration, transactions
MySQLTable MySQL / MariaDB dialect
SQLiteTable SQLite dialect

Pick the class that matches your connection:

The constructor rejects a connection whose driver does not match the class, so a mismatched pairing fails at once instead of emitting foreign SQL later. A script that must run on either can type-hint MiTableInterface.

Adding a dialect means writing one class that uses TableOperations and fills in four hooks: the driver check, identifier quoting, primary-key detection and offset paging. Everything else comes from the trait.

What this is, and what it is not

This package is the execution layer for migration scripts, not a migration framework.

It knows how to create a table, change its columns, write and transform its rows, and walk it without loading everything into memory. It does not know when a migration should run, whether it already ran, or how to undo it.

Layer Responsibility
Migration runner (not in this package) Migration registry, execution order, up/down, idempotency, automatic rollback, cross-table diffing
This package Single-table DDL, bulk writes, conditional updates and deletes, memory-safe iteration, schema introspection
Raw PDO (escape hatch) Joins, multi-table writes, complex conditions — reach them through getPdo()

Keeping version management and orchestration outside is deliberate: they need global state and would destroy the readability that makes this class useful.

The reasoning behind this split, the migration scenarios the class is built for, and the record of what changed and why are written up in docs/mitable-improvement-plan.html (in Chinese).

Features

Boundaries

In scope

Not in scope (by design)

Installation

Requires: PHP 8.1+, ext-pdo.

Quick Start

Where Conditions

Every condition is a single column => condition pair, and every value is bound as a parameter. The condition's shape decides the SQL:

Condition SQL
'alice' col = 'alice'
42 col = 42
null col IS NULL
['alice', 'bob'] col IN ('alice', 'bob')
['in', [1, 2]] col IN (1, 2)
['not in', [1, 2]] col NOT IN (1, 2)
['>=', 100] col >= 100
['between', [1, 10]] col BETWEEN 1 AND 10
['like', '%smith%'] col LIKE '%smith%'
['!=', null] col IS NOT NULL

Accepted operators: =, !=, <>, >, >=, <, <=, like, not like, in, not in, between, not between. Multiple conditions are joined with AND.

An empty list throws InvalidArgumentException rather than silently matching nothing, so a buggy condition cannot quietly turn into a full-table update. A condition that starts with an operator name must be a two-element [operator, value] pair; anything else of that shape throws instead of being reinterpreted as a list of values. That means a list whose first value happens to be an operator name needs the explicit form — ['in', ['in', 'out']], not ['in', 'out'].

This is intentionally not a query builder. Conditions stay flat: no nesting, no groups, no joins. When a condition no longer fits, that is the signal to use getPdo() and write the SQL directly.

Iterating Large Tables

Iteration pages by primary key cursor — WHERE id > :last ORDER BY id ASC LIMIT n — rather than LIMIT offset, n. Two things follow:

The cursor column is auto-detected from the table's single-column primary key. Composite-key and keyless tables fall back to OFFSET paging, which has no stable order.

Use withCursorKey() when the key is not a plain primary key:

The cursor column must be unique (a primary key or unique index). A non-unique cursor can skip rows that share a value.

Because iteration is keyed rather than counted, there is no COUNT(*) up front — starting a foreach on a huge table is cheap.

Resuming an interrupted run

A long scan can die partway through. cursor() reports the key of the row being handled, and withCursorStart() skips everything up to it:

Record the checkpoint after the row is finished. The value is reported when the row is read, so a run that dies while handling a row resumes on that same row rather than skipping it — the result is at-least-once, which is what an idempotent migration wants.

Resuming requires a single-column cursor key. On a keyless or composite-key table withCursorStart() throws when iteration begins rather than silently restarting from the first row, and cursor() returns null.

Schema Introspection

Migration scripts constantly need to ask "does this column exist yet?". showColumns() answers without raw SQL:

A missing table returns an empty array, so the same script works whether it is creating the table or altering it.

Each entry is keyed by column name and carries:

Field Meaning
name Column name
type Driver type string (e.g. VARCHAR(50), varchar(50))
nullable Whether NULL is allowed. Primary key columns always report false, matching MySQL
default Default expression, or null
primary Part of the primary key
unique Primary key or covered by a single-column unique index

showIndexes() answers the other half of the question — is there already an index by this name? — and sees indexes that span several columns, which showColumns() cannot:

Each entry is keyed by index name and carries:

Field Meaning
name Index name
columns Indexed columns, in index order
unique Whether the index enforces uniqueness
primary Whether it is the primary key
type Index type on MySQL (e.g. BTREE); empty when a driver does not report one

On SQLite an INTEGER PRIMARY KEY is the rowid alias and produces no index entry at all, so it never appears in showIndexes(); use showColumns() to detect that primary key. Indexes created by a UNIQUE or PRIMARY KEY constraint do appear, under a generated sqlite_autoindex_* name.

DDL Operations

Transactions

The transaction boundary stays the caller's. withTransaction() only removes the boilerplate around data changes:

The callback's return value is passed through. On any Throwable the transaction is rolled back and the exception is rethrown. No nesting or retry logic is added — for anything more involved, drive beginTransaction() yourself through getPdo().

Schema changes are not covered by this. MySQL commits implicitly around DDL, so an ALTER TABLE inside the callback survives a later failure: wrapping addColumn() in a transaction does not make it atomic. Split a migration so schema changes and data changes are separate steps, and make each step safe to re-run.

API Reference

Method Description
new MySQLTable(PDO $pdo, string $tableName) Constructor (MySQL / MariaDB)
new SQLiteTable(PDO $pdo, string $tableName) Constructor (SQLite)
getName() Get table name
getPdo() Get PDO instance
DDL
create($columns, $engine, $charset, $collate) Create table
drop() Drop table
exists() Check if table exists
rename($newName) Rename (returns new instance)
truncate() Truncate all rows
Columns
addColumn($name, $definition, $after = null) Add column
dropColumn($name) Drop column
modifyColumn($name, $definition) Modify column
renameColumn($old, $new, $definition) Rename column
Indexes
addIndex($name, $columns, $type = '') Add index
dropIndex($name) Drop index
addUniqueIndex($name, $columns) Add unique index
addPrimaryKey($columns) Add primary key
dropPrimaryKey() Drop primary key
Introspection
showColumns() Column metadata keyed by name
showIndexes() Index metadata keyed by name
CRUD
insert($data) Insert row, returns last insert ID
bulkInsert($rows) Bulk insert, returns affected count
update($data, $where) Update rows, returns affected count
delete($where) Delete rows, returns affected count
find($where) Find first matching row
where($where = [], $orderBy = '', $limit = null) Find all matching rows
count($where = []) Count matching rows
Iterator
withPageSize($pageSize) Set iteration page size
withCursorKey($column) Set the cursor column explicitly
withCursorStart($value) Resume iteration after a cursor value
cursor() Cursor key of the row being handled
foreach ($table as $row) Iterate over all rows
Transactions
withTransaction($callback) Run a callback atomically

Behaviour Notes

Driver Support

Each class drives one dialect and refuses the others:

MySQLTable SQLiteTable
Table options (ENGINE, CHARSET, COLLATE) applied not available, ignored
AFTER column positioning, USING index type supported not available, ignored
renameColumn() definition argument required ignored — SQLite keeps the existing type
modifyColumn() supported throws
addPrimaryKey() / dropPrimaryKey() supported throws
rename() / truncate() / renameColumn() supported supported
Introspection SHOW COLUMNS / SHOW INDEX PRAGMA table_info / PRAGMA index_list

SQLite cannot alter a table's primary key or a column's type in place, so those three operations throw a RuntimeException that points at the table-rebuild route, rather than emitting MySQL syntax that would surface as a bare syntax error. SQLiteTable::truncate() issues DELETE FROM and clears the sqlite_sequence counter, so it restarts the identity column the way MySQL's TRUNCATE does.

SQLiteTable needs SQLite 3.35 or newer for DROP COLUMN. For production MySQL, ext-pdo_mysql is required.

Design Philosophy

miGears miTable follows the miGears philosophy: minimal, readable, and useful.

What we don't do:

Testing

The tests come in two kinds. Conformance tests assert behaviour every dialect must share, and the same trait runs them twice — once on SQLite in the unit suite, once on MySQL in the integration suite. A behaviour only one dialect satisfies therefore fails. Dialect tests cover what is deliberately different: MySQL's AFTER positioning and USING index types, the three DDL verbs SQLite has no ALTER equivalent for, and SQLite's rowid-alias primary key.

This split exists because of a real gap: previously the SQLite and MySQL suites tested different things, so "cross-driver" was never a verified property, and the DDL verbs with no SQLite coverage at all went unnoticed.

The unit suite runs against an in-memory SQLite database and needs nothing else. The integration suite resolves a MySQL server in this order:

Order Source Example
1 MYSQL_DSN mysql://root:[email protected]:3306/migears_test
2 MYSQL_HOST with MYSQL_PORT, MYSQL_DB, MYSQL_USER, MYSQL_PASSWORD MYSQL_HOST=127.0.0.1
3 A throwaway container via docker or podman MYSQL_IMAGE=mysql:8.0 overrides the image
4 Nothing reachable every integration test is skipped

A container spawned for the run is removed when the process ends, and each test method starts from a schema with no tables. A MYSQL_DSN or MYSQL_HOST you provide is used as given: if it is wrong the suite fails instead of quietly falling back, so a misconfigured job cannot pass by accident.

CI runs both suites on every push across PHP 8.1–8.5, the integration one against a MySQL service container.

License

MIT


migears/mitable

Version

极简单表数据库操作助手,服务于一次性数据迁移 —— DDL、CRUD、结构反射与游标迭代,每个 SQL 方言一个实现类。

表操作类将 PDO 连接封装在单个表周围:指向一张表,改它的结构,搬运和转换其中的数据,然后功成身退。没有查询构建器依赖 —— 只有纯 PDO。

结构

组成 职责
MiTableInterface 契约:一个表实现必须做到的全部事情
TableOperations 与方言无关的那一半 —— CRUD、条件编译、游标迭代、事务
MySQLTable MySQL / MariaDB 方言
SQLiteTable SQLite 方言

按你的连接选择对应类:

构造函数会拒绝驱动与类不匹配的连接,因此配错会在构造时立刻失败,而不是在后续悄悄发出别家的 SQL。需要在两种库上都能跑的脚本,可以类型标注为 MiTableInterface。

新增一个方言,就是写一个使用 TableOperations 的类并实现四个钩子:驱动校验、标识符引用、主键探测、偏移分页。其余全部来自 trait。

这个包是什么,不是什么

这个包是迁移脚本的执行层,不是迁移框架。

它知道如何建表、改列、写入与转换行数据、以及在不把整表读进内存的前提下遍历全表。它不关心迁移该在什么时候跑、是否已经跑过、以及如何撤销。

层次 职责
迁移运行器(不在本包) 迁移记录表、执行顺序、up/down、幂等、自动回滚、跨表结构比对
本包 单表 DDL、批量写入、条件更新与删除、无内存遍历、结构反射
裸 PDO(逃生通道) JOIN、多表写入、复杂条件 —— 通过 getPdo() 直达

把版本管理和编排留在包外是刻意的:它们需要全局状态,一旦塞进来就会毁掉这个类赖以立足的可读性。

这条切分的理由、它服务的迁移场景,以及改了什么、为什么改的完整记录,见 docs/mitable-improvement-plan.html。

特性

边界

范围内

范围外(刻意不做)

安装

要求:PHP 8.1+,ext-pdo。

快速开始

查询条件

每个条件都是"列 => 条件"这一个键值对,所有值都作为参数绑定。条件的形态决定编译出的 SQL:

条件写法 生成的 SQL
'alice' col = 'alice'
42 col = 42
null col IS NULL
['alice', 'bob'] col IN ('alice', 'bob')
['in', [1, 2]] col IN (1, 2)
['not in', [1, 2]] col NOT IN (1, 2)
['>=', 100] col >= 100
['between', [1, 10]] col BETWEEN 1 AND 10
['like', '%smith%'] col LIKE '%smith%'
['!=', null] col IS NOT NULL

支持的操作符:=、!=、<>、>、>=、<、<=、like、not like、in、not in、between、not between。多个条件之间以 AND 连接。

空列表会抛出 InvalidArgumentException,而不是静默地匹配不到任何行 —— 一个写错的条件不该悄悄变成全表更新。以操作符名开头的条件必须是两元素的 [operator, value] 对;同形状的其他写法会抛异常,而不会被重新理解成一串取值。因此,当列表的首个取值恰好是操作符名时,需要写显式形式 —— ['in', ['in', 'out']],而不是 ['in', 'out']。

这里刻意不做查询构建器。条件保持扁平:没有嵌套、没有分组、没有 JOIN。当某个条件已经装不下时,那正是改用 getPdo() 直接写 SQL 的信号。

遍历大表

迭代按主键游标翻页 —— WHERE id > :last ORDER BY id ASC LIMIT n,而不是 LIMIT offset, n。这带来两点:

游标列会自动从表的单列主键探测。复合主键和无主键的表降级为 OFFSET 翻页,那种方式没有稳定顺序。

当主键不是普通主键时,用 withCursorKey() 显式指定:

游标列必须唯一(主键或唯一索引)。非唯一游标会跳过共享同一取值的行。

由于迭代是按游标推进而非计数,起始时没有 COUNT(*) —— 对超大表发起一次 foreach 是很轻的。

中断后续跑

长时间扫描可能中途断掉。cursor() 返回当前正在处理那一行的游标键,withCursorStart() 则跳过它之前的所有行:

请在一行处理完成之后记录断点。游标值是在读取该行时报告的,因此某行处理到一半崩溃,重跑会从这一行本身继续而不是跳过它——整体语义是"至少一次",这正是幂等迁移想要的。

续跑要求存在单列游标键。在无主键或复合主键的表上调用 withCursorStart() 会在开始遍历时抛异常,而不是悄悄从第一行重扫;这些情况下 cursor() 返回 null。

结构反射

迁移脚本总要问"这一列存在了吗"。showColumns() 让你不必写裸 SQL 就能回答:

表不存在时返回空数组,因此同一段脚本在"建表"和"改表"两种情况下都能工作。

返回值以列名作为键,每项包含:

字段 含义
name 列名
type 驱动返回的类型串(如 VARCHAR(50)、varchar(50))
nullable 是否允许 NULL。主键列一律返回 false,与 MySQL 保持一致
default 默认值表达式,无则为 null
primary 是否属于主键
unique 主键,或被单列唯一索引覆盖

showIndexes() 回答问题的另一半——"是否已有名为 idx_email 的索引"——并且能看见跨多列的索引,这是 showColumns() 做不到的:

返回值以索引名作为键,每项包含:

字段 含义
name 索引名
columns 索引包含的列,按索引顺序排列
unique 是否强制唯一
primary 是否为主键
type MySQL 上的索引类型(如 BTREE);驱动不报告时为空字符串

在 SQLite 上,INTEGER PRIMARY KEY 是 rowid 别名,完全不产生索引条目,因此不会出现在 showIndexes() 里;检测这种主键请用 showColumns()。由 UNIQUE 或 PRIMARY KEY 约束创建的索引会出现,名字是自动生成的 sqlite_autoindex_*。

DDL 操作

事务

事务边界仍归调用方,withTransaction() 只负责省掉数据变更周围的样板代码:

回调的返回值会被透传。一旦抛出 Throwable,事务回滚并重新抛出异常。不提供嵌套或重试 —— 更复杂的场景请通过 getPdo() 自行控制 beginTransaction()。

结构变更不在这个保护范围内。MySQL 在 DDL 前后会隐式提交,因此回调里的 ALTER TABLE 在后续失败后依然会留下:把 addColumn() 包进事务并不能让它具备原子性。请把迁移拆成"结构变更"与"数据变更"两个独立步骤,并让每一步都能安全重跑。

API 参考

方法 说明
new MySQLTable(PDO $pdo, string $tableName) 构造函数(MySQL / MariaDB)
new SQLiteTable(PDO $pdo, string $tableName) 构造函数(SQLite)
getName() 获取表名
getPdo() 获取 PDO 实例
DDL
create($columns, $engine, $charset, $collate) 创建表
drop() 删除表
exists() 检查表是否存在
rename($newName) 重命名(返回新实例)
truncate() 清空所有行
列操作
addColumn($name, $definition, $after = null) 添加列
dropColumn($name) 删除列
modifyColumn($name, $definition) 修改列
renameColumn($old, $new, $definition) 重命名列
索引操作
addIndex($name, $columns, $type = '') 添加索引
dropIndex($name) 删除索引
addUniqueIndex($name, $columns) 添加唯一索引
addPrimaryKey($columns) 添加主键
dropPrimaryKey() 删除主键
结构反射
showColumns() 以列名为键返回列元数据
showIndexes() 以索引名为键返回索引元数据
CRUD
insert($data) 插入行,返回最后插入 ID
bulkInsert($rows) 批量插入,返回影响行数
update($data, $where) 更新行,返回影响行数
delete($where) 删除行,返回影响行数
find($where) 查找第一条匹配行
where($where = [], $orderBy = '', $limit = null) 查找所有匹配行
count($where = []) 统计匹配行数
迭代器
withPageSize($pageSize) 设置迭代页大小
withCursorKey($column) 显式指定游标列
withCursorStart($value) 从指定游标值之后继续遍历
cursor() 当前正在处理那一行的游标键
foreach ($table as $row) 遍历所有行
事务
withTransaction($callback) 原子地执行一个回调

行为说明

驱动支持

每个类只驱动一种方言,并拒绝其他方言:

MySQLTable SQLiteTable
表选项(ENGINE、CHARSET、COLLATE) 生效 不存在,忽略
AFTER 列定位、USING 索引类型 支持 不存在,忽略
renameColumn() 的 $definition 参数 必需 忽略 —— SQLite 保留原有类型
modifyColumn() 支持 抛异常
addPrimaryKey() / dropPrimaryKey() 支持 抛异常
rename() / truncate() / renameColumn() 支持 支持
结构反射 SHOW COLUMNS / SHOW INDEX PRAGMA table_info / PRAGMA index_list

SQLite 无法就地修改主键或列类型,因此上述三个方法会抛出 RuntimeException,并在消息里指明需要重建表,而不是发出 MySQL 语法、让使用者看到一个没头没尾的语法错误。SQLiteTable::truncate() 使用 DELETE FROM 并清空 sqlite_sequence 计数,因此和 MySQL 的 TRUNCATE 一样会重置自增列。

SQLiteTable 的 DROP COLUMN 需要 SQLite 3.35 或更高版本。生产环境 MySQL 部署需要 ext-pdo_mysql。

设计哲学

miGears miTable 遵循 miGears 设计哲学:极简、可读、实用。

我们不做的事:

测试

测试分两类。一致性测试断言所有方言必须一致的行为,同一个 trait 把它跑两遍——一遍在单元套件的 SQLite 上,一遍在集成套件的 MySQL 上。因此"只有一个方言满足"的行为必然失败。方言测试覆盖刻意不同的部分:MySQL 的 AFTER 定位与 USING 索引类型、SQLite 上三个无 ALTER 等价写法的 DDL 动词,以及 SQLite 的 rowid 别名主键。

这个划分源于一个真实缺口:此前 SQLite 与 MySQL 两个套件测的是不同的东西,所以"跨驱动"从来不是一条被验证过的性质,那些在 SQLite 上零覆盖的 DDL 动词也就一直没被发现。

单元套件运行在内存 SQLite 上,不需要数据库服务。集成套件按以下顺序解析可用服务:

顺序 来源 示例
1 MYSQL_DSN mysql://root:[email protected]:3306/migears_test
2 MYSQL_HOST 配合 MYSQL_PORT、MYSQL_DB、MYSQL_USER、MYSQL_PASSWORD MYSQL_HOST=127.0.0.1
3 通过 docker 或 podman 起一个一次性容器 MYSQL_IMAGE=mysql:8.0 可覆盖镜像
4 都不可用 所有集成测试标记为跳过

运行期间起的容器会在进程结束时删除,每个测试方法都从"不含任何表"的空库开始。你显式提供的 MYSQL_DSN 或 MYSQL_HOST 会被原样使用:配错了就是失败,而不是悄悄降级,因此配置错误的流水线不会意外通过。

CI 在每次推送时都会跑这两个套件,PHP 版本覆盖 8.1–8.5,其中集成套件跑在 MySQL service container 上。

许可证

MIT


All versions of mitable with dependencies

PHP Build Version
Package Version
Requires php Version ^8.1
ext-pdo Version *
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package migears/mitable contains the following files

Loading the files please wait ...