cschmitz/l5simplefm

为 Soliant Consulting 的 SimpleFM 包提供的 Laravel 5 包装器。

1.0.3 2016-07-11 20:55 UTC

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');
	}
}

所需工具

以下工具是运行此项目所必需的

安装

  • 创建您的 Laravel 项目

  • 将包添加到您的 composer.json 文件中

      require: {
          "cschmitz/l5simplefm": "0.1.*"
      }
    
  • 运行 composer installcomposer 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 模型准备好在布局上查找所有记录。
  • $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()

要执行任何这些命令,您需要调用或链式调用 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)

查找所有记录返回给定实体(布局)的所有记录。 maxskip 参数允许您限制记录数和遍历数据。

如果我们想按“页”返回布局中的所有记录,其中

  • 每页记录数为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进行故障排除

对于RecordsNotFoundExceptionGeneralException类,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 异常的唯一原因是为了能够将命令结果返回。