cschmitz / l5simplefm
为 Soliant Consulting 的 SimpleFM 包提供的 Laravel 5 包装器。
Requires
- soliantconsulting/simplefm: 3.0.*
This package is not auto-updated.
Last update: 2024-09-28 18:42:18 UTC
README
L5SimpleFM 是围绕 Soliant Consulting 的 SimpleFM 包构建的工具。L5SimpleFM 允许你对托管在 FileMaker 数据库上的数据库进行声明性查询。
该工具专门为 Laravel 5 集成而创建,可以通过 composer 从 packagist 仓库 安装。
可以使用 L5SimpleFM 构建的示例任务项目可以在 这里在 github 上找到。
README 内容
快速示例
在 FileMaker 数据库的 web_Users
布局中执行查找,用户 web_Users::username
的值为 chris.schmitz,并且 web_Users::status
的值为 active,将如下所示
try {
$searchFields = ['username' => 'chris.schmitz', 'status' => 'active'];
$result = $fm->setLayout('web_Users')->findByFields($searchFields)->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('records');
L5SimpleFM 还允许你为 FileMaker 文件中的单个实体定义模型类。使用上述相同的 web_Users
示例,定义一个 L5SimpleFM FileMaker 模型将如下所示
<?php
namespace MyApp\Models;
use L5SimleFM\FileMakerModels\BaseModel;
class User extends BaseModel
{
protected $layoutName = "web_Users";
}
使用新定义的 User
模型执行第一个示例中的查找将如下所示
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\User;
class UsersController extends Controller
{
protected $user;
public function __construct(User $users)
{
$this->user = $users;
}
public function findUsers()
{
$searchFields = ['username' => 'chris.schmitz', 'status' => 'active'];
$result = $this->user->findByFields($searchFields)->executeCommand();
$records = $result->getRows();
return compact('records');
}
}
或者通过将方法添加到模型类本身
<?php
namespace MyApp\Models;
use L5SimleFM\FileMakerModels\BaseModel;
class User extends BaseModel
{
protected $layoutName = "web_Users";
public function findUserByUsername($username, $status = 'active'){
$searchFields = ['username' => $username, 'status' => $status];
$result = $this->findByFields($searchFields)->executeCommand();
return $result->getRows();
}
}
然后在控制器中使用该方法
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\User;
class UsersController extends Controller
{
protected $user;
public function __construct(User $users)
{
$this->user = $users;
}
public function findUsers()
{
$user = $this->user->findUserByUsername('chris.schmitz');
return compact('user');
}
}
所需工具
以下工具是运行此项目所必需的
- PHP 版本 5.5 或更高
- Composer
- 如果您是第一次安装 composer,请确保您 全局安装 composer
- Laravel 5.1
- Git
- FileMaker Server 版本 13 或更高
安装
-
创建您的 Laravel 项目
-
将包添加到您的
composer.json
文件中require: { "cschmitz/l5simplefm": "0.1.*" }
-
运行
composer install
或composer update
以拉取包。 -
安装包后,将 L5SimpleFM 服务提供者添加到
config/app.php
中的providers
键L5SimpleFM\L5SimpleFMServiceProvider::class,
配置
FileMaker
- 请确保您的 FileMaker 数据库
- 托管在您的 web 服务器可以访问的 FileMaker 服务器上。
- 有一个网站用户的 安全账户。
- 为网站用户设置的权限集已启用
fmxml
扩展权限。
在本 README 文件中,我将使用并引用此项目的 演示文件。
Laravel
-
将您的
.env.example
文件重命名为.env
-
从命令行,cd 到您的项目的根目录(您应该能看到
artisan
工具)并运行命令以生成应用程序密钥php artisan key:generate
-
在 Laravel 项目中,更新
.env
-
添加以下键和值
FM_DATABASE=
- 值应该是您数据库文件名称,不包含扩展名。
FM_USERNAME=
- 值应该是网站安全账户名称。
FM_PASSWORD=
- 值应该是网站安全账户密码。
FM_HOST=
- 值应该是您的 FileMaker 服务器的 IP 地址或域名。
FM_PROTOCOL=
- 您想要通信的协议。可以是 'http' 或 'https'。
- 如果没有提供环境值,默认为 'http'。
FM_PORT=
- 发送请求的端口号。
- 如果没有提供环境值,默认为 80。
FM_SSLVERIFYPEER=
- 您是否希望验证目标服务器的 SSL 证书。
- 如果没有提供环境值,默认为 true。
-
FM_
条目应类似于以下内容FM_DATABASE=L5SimpleFMExample FM_USERNAME=web_user FM_PASSWORD=webdemo! FM_HOST=127.0.0.1 FM_PROTOCOL=https FM_PORT=443 FM_SSLVERIFYPEER=true
-
重要提示
在生产环境中,永远不要将 L5SimpleFM 对象导出到浏览器!
SimpleFM 使用 FileMaker Server 的 XML 网络发布功能来访问 FileMaker。这意味着您的数据库凭据将通过请求传递。
如果您查看 L5SimpleFM->adapter->hostConnection
属性,您会看到这一点。
在开发调试时,导出对象非常有帮助,但在生产环境中导出对象则是一个安全风险。
演示 FileMaker 数据库
一个演示 FileMaker 数据库,L5SimpleFMExample.fmp12.zip,可以在发布部分找到。。
完全访问账户
- 用户名:Admin
- 密码:admin!password
注意:如果您打算将此示例文件托管在公开可访问的 FileMaker 服务器上,请更改完全访问账户密码!
网络访问账户
- 用户名:web_user
- 密码:webdemo!
L5SimpleFM 模型
L5SimpleFM 可以通过访问 L5SimpleFM 类或直接访问 FileMakerInterface 来仅作为基本数据访问工具使用,但它也可以用作数据模型。实际上,这两者之间的区别非常微小。基本思路是创建一个 L5SimpleFM 类的实例,该实例仅用于访问特定的实体(在 FileMaker 的例子中,这可能是通过布局访问的单个表)。
创建 L5SimpleFM 模型
L5SimpleFM 模型应扩展 L5SimpleFM\FileMakerModels\BaseModel
类
<?php
namespace App\Models;
use L5SimpleFM\FileMakerModels\BaseModel;
class User extends BaseModel
{
protected $layoutName = "web_User";
}
在上面的 Example
FileMaker 模型类中,我们的 FileMaker 文件中的布局应命名为 web_User
。
基本调用
一旦您已经
- 安装了捆绑包
- 托管并配置了您的 FileMaker 数据库
- 配置了您的 Laravel 项目
- 创建了一个模型
您可以在 Laravel 项目的 app/Http/routes.php
文件中打开。添加以下路由
<?php
use App\Models\User;
Route::get('users', function (User $user) {
try {
$user->findAll();
$user->max(10);
$user->sort([
['field' => 'company', 'rank' => 1, 'direction' => 'descend'],
['field' => 'status', 'rank' => 2, 'direction' => 'ascend'],
]);
$result = $user->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('records');
});
以下是 find 操作的每个步骤的分解
$user->findAll();
- 告诉您的 L5SimpleFM
User
模型准备好在布局上查找所有记录。
- 告诉您的 L5SimpleFM
$user->max(10)
- 告诉您的模型在执行命令时只返回最多
10
条记录。
- 告诉您的模型在执行命令时只返回最多
$user->sort([ ['field' => 'company', 'rank' => 1, 'direction' => 'descend'], ['field' => 'status', 'rank' => 2, 'direction' => 'ascend'] ])
- 告诉您的模型在执行命令后首先按公司字段降序排序,然后按状态字段升序排序。
$result = $user->executeCommand();
- 告诉 L5SimpleFM 执行您的命令。
- 命令的结果是一个包含请求结果元数据和结果的 SimpleFM(不是 L5SimpleFM)对象。
$records = $result->getRows();
- 从 SimpleFM 对象中提取记录。
方法链
L5SimpleFM 使用方法链,所以上面的查找所有演示也可以这样写
Route::get('users', function (User $user) {
try {
$sortFields = [
['field' => 'company', 'rank' => 1, 'direction' => 'descend'],
['field' => 'status', 'rank' => 2, 'direction' => 'ascend'],
];
$result = $user->findAll()->max(10)->sort($sortFields)->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('records');
});
这种使用方法链的方式可以使复杂请求更易于阅读。本说明书中其余的演示也将使用方法链。从现在起,您将可以访问 BaseModel
类 中概述的所有方法。这些方法实际上是映射到 L5SimpleFM
类的公共方法。
命令
- executeCommand()
- findByFields($fieldValues)
- findAll($max = null, $skip = null)
- findByRecId($recId)
- createRecord($data)
- 更新记录(updateRecord($recId, $data))
- 删除记录(deleteRecord($recId))
- 调用脚本(callScript($scriptName, $scriptParameters = null))
- 添加命令项(addCommandItems($commandArray))
- 最大值(max($count))
- 跳过(skip($count))
- 排序(sort($sortArray))
- 重置布局(resetLayout($layoutName))
executeCommand()
要执行任何这些命令,您需要调用或链式调用 executeCommand()
命令。
在 executeCommand()
之前链式的任何命令仅用于构建请求表单。这就是您可以单独调用或链式调用命令方法的原因。
以下是一个控制器上的索引方法的示例,该方法将方法调用拆分以构建一个对象,允许遍历记录集,并在设置好后执行一次 executeCommand()
。
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
// The L5SimpleFM Model for User
use App\Models\User;
class UsersController extends Controller
{
protected $user;
public function __construct(User $users)
{
$this->user = $users;
}
public function index(Request $request)
{
// capturing request headers passed in from the browser
$max = $request->get('max');
$skip = $request->get('skip');
$sortArray = [
['field' => 'company', 'rank' => 1, 'direction' => 'descend'],
['field' => 'username', 'rank' => 2, 'direction' => 'ascend'],
];
// note that we did not fire `executeCommand()` yet, we're still just building up the L5SimpleFM command
$this->user->findAll()->sort($sortArray);
// we don't want to specify a max value unless the browser actually asked for it
if (!empty($max)) {
$this->user->max($max);
}
// we don't want to specify a skip value unless the browser actually asked for it
if (!empty($skip)) {
$this->user->skip($skip);
}
// now that our command has been assembled, we fire it
$result = $this->user->executeCommand();
// getting the total number of records found (which may be larger than our max value)
$total = $result->getCount();
$records = $result->getRows();
return compact('total', 'records');
}
findAll($max = null, $skip = null)
查找所有记录返回给定实体(布局)的所有记录。 max
和 skip
参数允许您限制记录数和遍历数据。
如果我们想按“页”返回布局中的所有记录,其中
- 每页记录数为10
- 我们处于第3页
我们可以执行如下命令
// in your controller, these values would be passed in by the request parameters
$max = 10;
$skip = 2;
try {
$result = $this->user->findAll()->max($max)->skip($skip)->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('records');
findByFields($fieldValues)
L5SimpleFM接受一个关联数组[字段名 => 搜索值]
用于搜索。
例如,如果我们想从公司Skeleton Key的 web_Users
布局中找到所有状态为Active的记录,我们可以使用以下命令链
try {
$searchFields = [
'company' => 'Fake Company, INC',
'status' => 'Active',
];
$result = $this->user->findByFields($searchFields)->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('records');
findByRecId($recId)
FileMaker为每个创建的记录使用一个内部记录id,无论您是否在表中添加序列号字段。您可以在FileMaker中通过转到要搜索的布局、打开数据查看器并输入函数Get(RecordId)
来查看此记录id。
L5SimpleFM有一个专门用于按此记录id搜索的方法。
例如。要找到具有recid 3的 web_Users
表中的记录,我们可以使用以下命令链
try {
$result = $this->user->findByRecId(3)->executeCommand();
$record = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('record');
调用脚本(callScript($scriptName, $scriptParameters = null))
可以将脚本设置在L5SimpleFM执行不同命令后触发。
以下是在执行 findByRecId 命令后触发的相同日志脚本
try {
$searchFields = ['username' => 'chris.schmitz'];
$message = sprintf("Creating a log record after performing a find for the user record with username %s.", $searchFields['username']);
$result = $this->user->findByFields($searchFields)->callScript('Create Log', $message)->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('records');
createRecord($data)
可以使用关联数组[字段名 => 搜索值]
创建新记录。
try {
$recordValues = [
'username' => 'new.person',
'email' => 'new.person@skeletonkey.com',
'company' => 'Skeleton Key'
];
$result = $this->user->createRecord($recordValues)->executeCommand();
$record = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('record');
更新记录(updateRecord($recId, $data))
像创建新记录一样,可以使用关联数组[字段名 => 搜索值]
更新记录。
未包含在数组中的字段将不会被修改,因此只需指定您要更改的内容。如果需要清除字段,请传入空字符串。
要更新记录,您需要特定记录的记录id。
try {
$updatedValues = [
'username' => 'fired.person',
'email' => '',
'company' => '',
'status' => 'Inactive'
];
$recid = 2;
$message = sprintf('User %s no longer works for Skeleton Key', $updatedValues['username']);
$result = $this->user->updateRecord($recid, $updatedValues)->callScript('Create Log', $message)->executeCommand();
$record = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('record');
删除记录(deleteRecord($recId))
要删除记录,请指定记录id。
请注意,我们不需要设置$result
变量,因为在成功删除记录时没有要检索的记录。删除记录的任何错误都将被catch
异常捕获。
try {
$recid = 10;
$this->user->deleteRecord($recid)->executeCommand();
} catch (\Exception $e) {
return $e->getMessage();
}
return ['success' => 'Record Deleted'];
添加命令项(addCommandItems($commandArray))
还有许多其他自定义Web发布XML命令,您可以通过SimpleFM发送到FileMaker服务器,而不仅仅是我在这里概述的。我还尝试涵盖了一些最常见(以及我在从该包装器提取的项目中需要的一些)命令。您还可以在特定请求中传递其他命令。
命令通过请求url中的键/值对发送。您可以在FileMaker服务器PDF "fmsXX_cwp_xml.pdf" 中看到这些文档,其中XX是您访问的FileMaker服务器版本号(例如,fms13_cwp_xml.pdf)。
如果您想发送由L5SimpleFM类未定义的命令到FileMaker服务器,可以使用customCommand
方法。您可以传递一个关联数组[command => value]对来添加到请求url中。
例如,如果我们想使用findAll
命令设置返回的最大记录数,我们可以在请求中添加-max
命令
try {
$maxRecordsToReturn = 3;
$result = $this->user->findAll()->addCommandItems(['-max' => $maxRecordsToReturn])->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return $records;
您还可以使用此方法构建任何通过SimpleFM发送的命令,包括L5SimpleFM类方法中未包含的命令。如果我们想手动创建一个findByFields
命令,可以这样操作
try {
$commandArray = [
'status' => 'Active',
'-max' => 3,
'-find' => null
];
$result = $this->user->addCommandItems($commandArray)->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('records');
最大值(max($count))
对于返回记录数量可变的命令,可以将max()
链接到命令中,以限制返回的记录数量
try {
$searchFields = [
'company' => 'Skeleton Key',
'status' => 'Active',
];
$count = 50;
$result = $this->user->findByFields($searchFields)->max($count)->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('records');
虽然找到的记录总数可能大于50,但只有50条记录将在行中返回。
跳过(skip($count))
类似于max()
命令,skip()
命令可以添加到返回可变数量记录的命令中,以影响返回的记录。Skip将确定返回有限数量记录时从哪条记录开始。
try {
$searchFields = [
'company' => 'Fake Company, INC',
'status' => 'Active',
];
$count = 50;
$skip = 2;
$result = $this->user->findByFields($searchFields)->max($count)->skip($skip)->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('records');
在这个例子中,我们只返回最多50条记录,并从找到的集合中的第11条记录开始(我们跳过了前10条)。
skip()
和max()
可以组合使用,以便于在找到的记录集中进行分页。
排序(sort($sortArray))
L5SimpleFM接受一个多维数组数据来执行排序。
使用排序时,您必须指定
- 用于排序的字段
- 字段应排序的顺序(或顺序)
如果我们想按company
和然后按username
排序,可以使用以下数组结构
$sortOptions = [
['field' => 'company', 'rank' => 1],
['field' => 'username', 'rank' => 2]
];
您还可以可选地指定字段可以排序的方向。
$sortOptions = [
['field' => 'company', 'rank' => 1, 'direction' => 'descend'],
['field' => 'username', 'rank' => 2, 'direction' => 'ascend']
];
一旦构建了您的排序选项数组,就可以将它们传递给sort()
命令
try {
$sortOptions = [
['field' => 'company', 'rank' => 1, 'direction' => 'descend'],
['field' => 'username', 'rank' => 2, 'direction' => 'ascend'],
];
$result = $this->user->findAll()->sort($sortOptions)->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('records');
重置布局(resetLayout($layoutName))
在我看来,FileMaker自定义网络发布的一个缺点是您无法指定请求返回的字段;您始终从您请求的布局上的每个字段中获取数据。从SQL的角度来看,通过自定义网络发布对FileMaker服务器的请求始终是SELECT * FROM mylayout ...
而不是SELECT field1,field3,fieldN FROM mylayout ...
。
因此,我添加了在运行时重置您正在使用的布局的能力。
try {
$searchFields = [
'company' => 'Fake Company, INC',
'status' => 'Active',
];
$result = $this->user->resetLayout('web_UserList')->findByFields($searchFields)->max($count)->skip($skip)->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
return compact('records');
这意味着您可以有多个表示实体的布局。
一个实际的例子是,如果您的Web应用有一个使用实体中的一系列列来让用户识别每条记录的列表。点击行将打开一个新窗口,显示实体的所有字段。如果您只使用一个布局来表示实体,那么在生成列表视图时,您总是会返回实体的所有字段,即使您不需要它们。
使用resetLayout
命令,您可以定义两个布局,一个只包含您需要用于列表的字段列表布局,一个包含您需要用于详细信息窗口的所有字段详细信息布局。当您需要使用备用布局(在模型中未定义为$layoutName
属性的布局)时,可以使用resetLayout
命令来对该备用布局发出请求。
异常
L5SimpleFM抛出的所有异常都来自类L5SimpleFMBase
。可以通过其个别名称捕获异常,例如
try {
$result = $this->user->findByFields(['company' => 'error co.'])->executeCommand();
$records = $result->getRows();
} catch (RecordsNotFoundException $e) {
return $e->getMessage();
}
或者通过捕获通用的PHP异常类(所有自定义异常都从它扩展而来)
try {
$result = $this->user->findByFields(['company' => 'error co.'])->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
return $e->getMessage();
}
使用getCommandResult进行故障排除
对于RecordsNotFoundException
和GeneralException
类,SimpleFM请求的结果对象将被返回,并且可以通过方法->getCommandResult()
访问。例如
try {
$searchFields = [
// there is no Error Company and the `RecordsNotFoundException` will be thrown
'company' => 'Error Company',
'status' => 'Active',
];
$result = $this->user->findByFields($searchFields)->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
$message = $e->getMessage();
// You can use the `getCommandResult()` method to return
// the entire SimpleFM result object.
$result = $e->getCommandResult();
dd($result);
return $message;
}
return compact('records');
}
这很有帮助,因为SimpleFM的FmResultSet对象包括一个调试URL,有助于找出结果失败的原因
FmResultSet {#153 ▼
#debugUrl: "http://web_user:[...]@127.0.0.1:80/fmi/xml/fmresultset.xml?-db=L5SimpleFMExample&-lay=web_Users&-db=L5SimpleFMExample&-lay=web_Users&company=Error+Co&status=Active&-find"
#errorCode: 401
#errorMessage: "No records match the request"
#errorType: "FileMaker"
#count: 0
#fetchSize: 0
#rows: []
}
这也意味着您可以使用FmResultSet的其他结果处理方法,如getDebugUrl()
try {
$searchFields = [
// there is no Error Company and the `RecordsNotFoundException` will be thrown
'company' => 'Error Company',
'status' => 'Active',
];
$result = $this->user->findByFields($searchFields)->executeCommand();
$records = $result->getRows();
} catch (\Exception $e) {
$message = $e->getMessage();
$result = $e->getCommandResult();
// spits out the debug url itself
dd($result->getDebugUrl());
return $message;
}
return compact('records');
}
注意,尽管 FmResultSet 和调试 URL 不会暴露您的密码,但在将项目推送到生产环境时仍然不建议将其留在项目中,即:仅用于开发调试。
LayoutNameIsMissingException
如果在未设置值或使用空字符串的情况下尝试设置布局名称,则会抛出此异常。
如果您正在使用 L5SimpleFM 创建 FileMaker 模型,则如果没有指定 protected $layoutName;
属性,您将看到此错误。
此异常不包含结果对象。
NoResultReturnedException
如果由于某些原因 SimpleFM 没有返回结果对象,则会抛出此异常。
此异常不包含结果对象。
RecordsNotFoundException
如果您的查询未返回结果,则会返回此异常。
我为此创建了特定的异常,因为它是一个您可能会忽略的错误。
例如,如果您正在查找现有用户记录并在找不到现有记录时创建新记录,您可以选择捕获异常并向应用程序的其他部分标记创建新记录。
public function updateOrCreateNewUser(Request $request)
{
// email passed in from a POST request
$email = $request->get('email');
$userRecord = $this->checkForExistingUserRecord($email);
if ($userRecord == false) {
$record = $this->createNewUser($request->all());
} else {
$record = $this->updateExistingUser($userRecord['recid'], $request->all());
}
return compact('record');
}
protected function checkForExistingUserRecord($email)
{
try {
$quotedEmail = sprintf('"%s"', $email);
$result = $this->user
->findByFields(['email' => $quotedEmail])
->executeCommand();
$record = $result->getRows()[0];
} catch (RecordsNotFoundException $e) {
return false;
}
return $record;
}
在其他情况下,您可能希望将找不到记录的异常视为其他任何异常。
RecordsNotFoundException 确实 返回一个命令结果。
GeneralException
这是在 SimpleFM 请求
- 确实 返回了结果
- 结果 确实 包含错误
- 错误不是找不到记录的错误。
这将是任何其他 FileMaker XML 自定义网络发布错误。
GeneralException 确实 返回一个命令结果。
实际上,我定义一个通用异常而不是抛出一个常规的 PHP 异常的唯一原因是为了能够将命令结果返回。