Skip to content

Commit 2a723f7

Browse files
committed
Enum classes with const for Table version type;
Enum classes with const for Table field type; Test for deleted row;
1 parent df7332d commit 2a723f7

File tree

8 files changed

+211
-15
lines changed

8 files changed

+211
-15
lines changed

src/XBase/Enum/FieldType.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace XBase\Enum;
4+
5+
final class FieldType
6+
{
7+
/** @var string Memo type field */
8+
const MEMO = 'M';
9+
/** @var string Character field */
10+
const CHAR = 'C';
11+
/** @var string Double */
12+
const DOUBLE = 'B';
13+
/** @var string Numeric */
14+
const NUMERIC = 'N';
15+
/** @var string Floating point */
16+
const FLOATING = 'F';
17+
/** @var string Date */
18+
const DATE = 'D';
19+
/** @var string Logical - ? Y y N n T t F f (? when not initialized). */
20+
const LOGICAL = 'L';
21+
/** @var string DateTime */
22+
const DATETIME = 'T';
23+
/** @var string Index */
24+
const INDEX = 'I';
25+
/** @var string Ignore this field */
26+
const DBFFIELD_IGNORE_0 = '0';
27+
}

src/XBase/Enum/TableFlag.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace XBase\Enum;
4+
5+
/**
6+
* MDX flags
7+
*/
8+
final class TableFlag
9+
{
10+
const NONE = 0x00;
11+
/** @var int File has a structural .cdx */
12+
const CDX = 0x01;
13+
/** @var int File has a Memo field */
14+
const MEMO = 0x02;
15+
/** @var int File is a database (.dbc) */
16+
const DBC = 0x04;
17+
}

src/XBase/Enum/TableType.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace XBase\Enum;
4+
5+
final class TableType
6+
{
7+
/** @var int dBase II or FoxBASE */
8+
const DBASE_II = 0x02;
9+
/** @var int FoxBASE+/dBase III plus, no memo */
10+
const DBASE_III_PLUS_NOMEMO = 0x03;
11+
/** @var int dBase 7 */
12+
const DBASE_7_NOMEMO = 0x04;
13+
/** @var int Visual FoxPro */
14+
const VISUAL_FOXPRO = 0x30;
15+
/** @var int Visual FoxPro, autoincrement enabled */
16+
const VISUAL_FOXPRO_AI = 0x31;
17+
/** @var int Visual FoxPro, Varchar, Varbinary, or Blob-enabled */
18+
const VISUAL_FOXPRO_VAR = 0x32;
19+
/** @var int dBase IV/dBase 5 - SQL table files, no memo */
20+
const DBASE_IV_SQL_TABLE_NOMEMO = 0x43;
21+
/** @var int dBase IV/dBase 5 SQL system files, no memo */
22+
const DBASE_IV_SQL_SYSTEM_NOMEMO = 0x63;
23+
/** @var int FoxBASE+/dBase III PLUS/FoxPro, with memo (*.DBT) */
24+
const DBASE_III_PLUS_MEMO = 0x83;
25+
/** @var int dBase IV/dBase 5, with memo (*.DBT) */
26+
const DBASE_IV_MEMO_PLUS_MEMO = 0x8B;
27+
/** @var int dBase 7, with memo (*.DBT) */
28+
const DBASE_7_MEMO = 0x8C;
29+
/** @var int dBase IV/dBase 5, SQL table files, with memo (*.DBT) */
30+
const DBASE_IV_SQL_TABLE_MEMO = 0xCB;
31+
/** @var int (*.SMT) */
32+
const SMT = 0xE5;
33+
/** @var int dBase IV/dBase 5, SQL system files, with memo */
34+
const DBASE_IV_SQL_SYSTEM_MEMO = 0xEB;
35+
/** @var int FoxPro 2.x ( or earlier) with memo (*.FTP) */
36+
const FOXPRO_MEMO = 0xF5;
37+
/** @var int FoxBASE */
38+
const FOXBASE = 0xFB;
39+
40+
public static function isFoxpro(int $version): bool
41+
{
42+
return in_array($version, [
43+
self::VISUAL_FOXPRO,
44+
self::VISUAL_FOXPRO_AI,
45+
self::DBASE_III_PLUS_MEMO,
46+
self::DBASE_IV_SQL_TABLE_MEMO,
47+
self::FOXPRO_MEMO,
48+
self::FOXBASE,
49+
]);
50+
}
51+
}

src/XBase/Record.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
class Record
88
{
9+
const FLAG_NOT_DELETED = 0x20;
10+
const FLAG_DELETED = 0x2a;
11+
912
/** @var string Memo type field */
1013
const DBFFIELD_TYPE_MEMO = 'M';
1114
/** @var string Character field */
@@ -58,7 +61,7 @@ public function __construct(Table $table, $recordIndex, $rawData = false)
5861

5962
if ($rawData && strlen($rawData) > 0) {
6063
$this->inserted = false;
61-
$this->deleted = (ord($rawData[0]) != '32');
64+
$this->deleted = (ord($rawData[0]) !== self::FLAG_NOT_DELETED);
6265

6366
foreach ($table->getColumns() as $column) {
6467
$this->choppedData[$column->getName()] = substr($rawData, $column->getBytePos(), $column->getDataLength());
@@ -382,7 +385,7 @@ public function getIndex($columnName, $length)
382385
return false;
383386
}
384387

385-
if ($this->table->foxpro) {
388+
if ($this->table->isFoxpro()) {
386389
$su = unpack("i", $s);
387390
$ret = $su[1];
388391
} else {
@@ -658,7 +661,7 @@ public function setInt($columnObj, $value)
658661
*/
659662
public function serializeRawData()
660663
{
661-
return ($this->deleted ? '*' : ' ').implode('', $this->choppedData);
664+
return chr($this->deleted ? self::FLAG_DELETED : self::FLAG_NOT_DELETED).implode('', $this->choppedData);
662665
}
663666

664667
/**

src/XBase/Table.php

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22

33
namespace XBase;
44

5+
use XBase\Enum\TableType;
56
use XBase\Exception\TableException;
67

78
class Table
89
{
9-
/** @var string */
10+
/** @var int Table header length in bytes */
11+
const HEADER_LENGTH = 32;
12+
/** @var int Record field length in bytes */
13+
const FIELD_LENGTH = 32;
14+
15+
/** @var string Table filepath. */
1016
protected $tableName;
1117
/** @var array|null */
1218
protected $availableColumns;
@@ -44,7 +50,10 @@ class Table
4450
/** @var int */
4551
public $headerLength;
4652
public $backlist;
47-
/** @var bool */
53+
/**
54+
* @var bool
55+
* @deprecated since 1.1 and will be removed in 2.0. Use isFoxpro method instead.
56+
*/
4857
public $foxpro;
4958
/** @var Memo */
5059
public $memoFile;
@@ -85,7 +94,7 @@ protected function open()
8594
protected function readHeader()
8695
{
8796
$this->version = $this->readChar();
88-
$this->foxpro = in_array($this->version, [48, 49, 131, 203, 245, 251]);
97+
$this->foxpro = TableType::isFoxpro($this->version);
8998
$this->modifyDate = $this->read3ByteDate();
9099
$this->recordCount = $this->readInt();
91100
$this->headerLength = $this->readShort();
@@ -99,15 +108,15 @@ protected function readHeader()
99108
$this->languageCode = $this->readByte();
100109
$this->readBytes(2); //reserved
101110

102-
$fieldCount = floor(($this->headerLength - ($this->foxpro ? 296 : 33)) / 32);
111+
$fieldCount = floor(($this->headerLength - ($this->isFoxpro() ? 296 : 33)) / self::FIELD_LENGTH);
103112

104113
/* some checking */
105114
if ($this->headerLength > filesize($this->tableName)) {
106-
throw new Exception\TableException(sprintf('File %s is not DBF', $this->tableName));
115+
throw new TableException(sprintf('File %s is not DBF', $this->tableName));
107116
}
108117

109118
if ($this->headerLength + ($this->recordCount * $this->recordByteLength) - 500 > filesize($this->tableName)) {
110-
throw new Exception\TableException(sprintf('File %s is not DBF', $this->tableName));
119+
throw new TableException(sprintf('File %s is not DBF', $this->tableName));
111120
}
112121

113122
/* columns */
@@ -140,7 +149,7 @@ protected function readHeader()
140149
}
141150
}
142151

143-
if ($this->foxpro) {
152+
if ($this->isFoxpro()) {
144153
$this->backlist = $this->readBytes(263);
145154
}
146155

@@ -392,6 +401,11 @@ protected function isOpen()
392401
return $this->fp ? true : false;
393402
}
394403

404+
public function isFoxpro(): bool
405+
{
406+
return TableType::isFoxpro($this->version);
407+
}
408+
395409
/**
396410
* @param int $length
397411
*

src/XBase/WritableTable.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22

33
namespace XBase;
44

5+
use XBase\Enum\TableType;
6+
57
class WritableTable extends Table
68
{
79
/**
810
* @param $table
911
*
1012
* @return WritableTable
1113
*/
12-
public function cloneFrom($table)
14+
public function cloneFrom(Table $table)
1315
{
1416
$result = new WritableTable($table->tableName);
1517
$result->version = $table->version;
@@ -24,7 +26,7 @@ public function cloneFrom($table)
2426
$result->columnNames = $table->columnNames;
2527
$result->headerLength = $table->headerLength;
2628
$result->backlist = $table->backlist;
27-
$result->foxpro = $table->foxpro;
29+
$result->foxpro = $table->isFoxpro();
2830

2931
return $result;
3032
}
@@ -42,8 +44,8 @@ public function create($filename, $fields)
4244
}
4345

4446
$recordByteLength = 1;
45-
$columns = array();
46-
$columnNames = array();
47+
$columns = [];
48+
$columnNames = [];
4749
$i = 0;
4850

4951
foreach ($fields as $field) {
@@ -58,7 +60,7 @@ public function create($filename, $fields)
5860
}
5961

6062
$result = new WritableTable($filename);
61-
$result->version = 131;
63+
$result->version = TableType::DBASE_III_PLUS_MEMO;
6264
$result->modifyDate = time();
6365
$result->recordCount = 0;
6466
$result->recordByteLength = $recordByteLength;

tests/SimpleTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use PHPUnit\Framework\TestCase;
66
use XBase\Column;
7+
use XBase\Enum\TableType;
8+
use XBase\Enum\TableFlag;
79
use XBase\Record;
810
use XBase\Table;
911
use XBase\WritableTable;
@@ -20,12 +22,15 @@ public function testRead()
2022
self::assertSame(10, $table->getRecordCount());
2123

2224
self::assertSame(3, $table->version);
25+
self::assertSame(TableType::DBASE_III_PLUS_NOMEMO, $table->version);
2326
self::assertSame(false, $table->foxpro);
27+
self::assertSame(false, $table->isFoxpro());
2428
self::assertSame(1580774400, $table->modifyDate);
2529
self::assertSame(609, $table->headerLength);
2630
self::assertSame(225, $table->recordByteLength);
2731
self::assertSame(false, $table->inTransaction);
2832
self::assertSame(false, $table->encrypted);
33+
self::assertSame(TableFlag::NONE, bindec($table->mdxFlag));
2934
self::assertSame('00', bin2hex($table->mdxFlag));
3035
self::assertSame('00', bin2hex($table->languageCode));
3136

@@ -237,6 +242,31 @@ public function testWritableTableDeleteRecord()
237242
$copyTo = "{$info['dirname']}/$newName.{$info['extension']}";
238243
self::assertTrue(copy(self::FILEPATH, $copyTo));
239244

245+
try {
246+
$table = new WritableTable($copyTo, null, 'cp866');
247+
$table->openWrite();
248+
$table->nextRecord(); // set pointer to first row
249+
$table->deleteRecord();
250+
$table->writeRecord();
251+
$table->close();
252+
253+
$table = new Table($copyTo, null, 'cp866');
254+
self::assertEquals(10, $table->getRecordCount());
255+
$record = $table->pickRecord(0);
256+
self::assertTrue($record->isDeleted());
257+
$table->close();
258+
} finally {
259+
unlink($copyTo);
260+
}
261+
}
262+
263+
public function testWritableTableDeletePackRecord()
264+
{
265+
$info = pathinfo(self::FILEPATH);
266+
$newName = uniqid($info['filename']);
267+
$copyTo = "{$info['dirname']}/$newName.{$info['extension']}";
268+
self::assertTrue(copy(self::FILEPATH, $copyTo));
269+
240270
try {
241271
$table = new WritableTable($copyTo, null, 'cp866');
242272
$table->openWrite();

tests/TableTypeTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace XBase\Tests;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use XBase\Enum\TableType;
7+
8+
class TableTypeTest extends TestCase
9+
{
10+
public function testUnique()
11+
{
12+
$array = [];
13+
$refClass = new \ReflectionClass(TableType::class);
14+
foreach ($refClass->getConstants() as $val) {
15+
self::assertFalse(in_array($val, $array));
16+
$array[] = $val;
17+
}
18+
}
19+
20+
public function testConst()
21+
{
22+
self::assertSame(0x02, TableType::DBASE_II);
23+
self::assertSame(0x03, TableType::DBASE_III_PLUS_NOMEMO);
24+
self::assertSame(0x04, TableType::DBASE_7_NOMEMO);
25+
self::assertSame(0x30, TableType::VISUAL_FOXPRO);
26+
self::assertSame(0x31, TableType::VISUAL_FOXPRO_AI);
27+
self::assertSame(0x32, TableType::VISUAL_FOXPRO_VAR);
28+
self::assertSame(0x43, TableType::DBASE_IV_SQL_TABLE_NOMEMO);
29+
self::assertSame(0x63, TableType::DBASE_IV_SQL_SYSTEM_NOMEMO);
30+
self::assertSame(0x83, TableType::DBASE_III_PLUS_MEMO);
31+
self::assertSame(0x8B, TableType::DBASE_IV_MEMO_PLUS_MEMO);
32+
self::assertSame(0x8C, TableType::DBASE_7_MEMO);
33+
self::assertSame(0xCB, TableType::DBASE_IV_SQL_TABLE_MEMO);
34+
self::assertSame(0xE5, TableType::SMT);
35+
self::assertSame(0xEB, TableType::DBASE_IV_SQL_SYSTEM_MEMO);
36+
self::assertSame(0xF5, TableType::FOXPRO_MEMO);
37+
self::assertSame(0xFB, TableType::FOXBASE);
38+
}
39+
40+
public function testIsFoxpro()
41+
{
42+
self::assertEquals(TableType::isFoxpro(1), false);
43+
self::assertEquals(TableType::isFoxpro(10), false);
44+
self::assertEquals(TableType::isFoxpro(TableType::DBASE_III_PLUS_NOMEMO), false);
45+
self::assertEquals(TableType::isFoxpro(TableType::VISUAL_FOXPRO), true);
46+
self::assertEquals(TableType::isFoxpro(TableType::VISUAL_FOXPRO_AI), true);
47+
self::assertEquals(TableType::isFoxpro(TableType::DBASE_III_PLUS_MEMO), true);
48+
self::assertEquals(TableType::isFoxpro(TableType::DBASE_IV_SQL_TABLE_MEMO), true);
49+
self::assertEquals(TableType::isFoxpro(TableType::FOXPRO_MEMO), true);
50+
self::assertEquals(TableType::isFoxpro(TableType::FOXBASE), true);
51+
}
52+
}

0 commit comments

Comments
 (0)