4

I'm using CakePHP 3.3.9 and trying to use the friendsofcake/search plugin + pagination to filter a list of AssessmentLogs based on Clients.EmrSystems, in which EmrSystems is a belongsToMany association on Clients. This is all on a SQL Server database if it makes a difference.

My problem is that when I use matching() and contain() I receive the correct results but the Client association is missing from the AssessmentLog record, even though it is explicitly contained. I'm not concerned about getting the EmrSystems for a Client under each AssessmentLog, only the Client that owns it.

The query generated even appears correct, but the ORM just has a null value for the client association on each record. Even _matchingData contains client according to DebugKit, so I know the right info is there. Manually running the generated query even returns the right results.

Here's how the associations are laid out:

AssessmentLogsTable

// AssessmentLog belongs to a Client using ClientId field
$this->belongsTo('Clients', [
    'foreignKey' => 'ClientId'
]);

// Search plugin
$this->searchManager()->add('EmrSystem', 'Search.Callback', [
    'callback' => function ($query, $args, $manager) {
        if (!is_array($args['EmrSystem'])) {
            return false;
        }

        // Not returning the Client association for some reason :(
        // Should return only assessment logs where the client has a specified EMR system.
        // The AssessmentLog should always contain the Client association

        return $query->contain([
            'Clients',
            'Clients.EmrSystems'
        ])
        ->matching('Clients.EmrSystems', function ($q) use ($args) {
            return $q->where(function ($exp) use ($args) {
                return $exp->in('EmrSystems.ID', $args['EmrSystem']);
            });
        });
    },
    'filterEmpty' => true
]);

ClientsTable

// Client has many assessment logs - The ID fields aren't named consistently and wasn't my choice or design. The field name is correct.
$this->hasMany('AssessmentLogs', [
    'foreignKey' => 'ClientID'
]);

// Client can have multiple EMR (Electronic Medical Record) systems
$this->belongsToMany('EmrSystems', [
    'joinTable' => 'ClientEmrSystem',
    'foreignKey' => 'ClientId',
    'targetForeignKey' => 'EmrSystemId',
    'through' => 'ClientEmrSystems',
    'saveStrategy' => 'replace'
]);

AssessmentLogsController

// Load other associations
$this->paginate['contain'] = [
    'AssessmentTypes' => function ($q) {
        return $q->select([
            'AssessmentTypes.AssessmentTypeCd',
            'AssessmentTypes.AssessmentTypeShort'
        ]);
    },
    'Clients' => function ($q) {
        return $q->select([
            'Clients.ClientId',
            'Clients.OrganizationName'
        ]);
    },
    'Patients' => function ($q) {
        return $q->select([
            'Patients.PatientId',
            'Patients.FirstName',
            'Patients.LastName'
        ]);
    }   
];

// Use Search Plugin
$assessmentLogs = $this->AssessmentLogs->find(
    'search', 
    $this->AssessmentLogs->filterParams($this->request->query)
);

$this->set('assessmentLogs', $this->paginate($assessmentLogs));

Generated Query

I've included some other associations that are working correctly and returning patient names, types, etc.

SELECT 
  AssessmentLogs.AssessmentLogId AS [AssessmentLogs__AssessmentLogId], 
  AssessmentLogs.ClientID AS [AssessmentLogs__ClientID], 
  AssessmentLogs.PatientID AS [AssessmentLogs__PatientID], 
  AssessmentLogs.AssessmentTypeCd AS [AssessmentLogs__AssessmentTypeCd], 
  Clients.ClientId AS [Clients__ClientId], 
  Clients.OrganizationName AS [Clients__OrganizationName], 
  ClientEmrSystems.ClientId AS [ClientEmrSystems__ClientId], 
  ClientEmrSystems.EmrSystemId AS [ClientEmrSystems__EmrSystemId], 
  ClientEmrSystems.Created AS [ClientEmrSystems__Created], 
  ClientEmrSystems.Modified AS [ClientEmrSystems__Modified], 
  EmrSystems.ID AS [EmrSystems__ID], 
  EmrSystems.Name AS [EmrSystems__Name], 
  EmrSystems.Created AS [EmrSystems__Created], 
  EmrSystems.Modified AS [EmrSystems__Modified], 
  AssessmentTypes.AssessmentTypeCd AS [AssessmentTypes__AssessmentTypeCd], 
  AssessmentTypes.AssessmentTypeShort AS [AssessmentTypes__AssessmentTypeShort], 
  Patients.PatientId AS [Patients__PatientId], 
  Patients.FirstName AS [Patients__FirstName], 
  Patients.LastName AS [Patients__LastName]
FROM 
  AssessmentLog AssessmentLogs 
  INNER JOIN Client Clients ON Clients.ClientId = (AssessmentLogs.ClientId) 
  INNER JOIN ClientEmrSystem ClientEmrSystems ON Clients.ClientId = (ClientEmrSystems.ClientId) 
  INNER JOIN EmrSystem EmrSystems ON EmrSystems.ID = (ClientEmrSystems.EmrSystemId) 
  LEFT JOIN AssessmentType AssessmentTypes ON AssessmentTypes.AssessmentTypeCd = (
    AssessmentLogs.AssessmentTypeCd
  ) 
  LEFT JOIN Patient Patients ON Patients.PatientId = (AssessmentLogs.PatientId) 
WHERE 
  (
    EmrSystems.ID in (1) 
    AND (
      AssessmentLogs.Void = 0 
    )
  ) 
ORDER BY 
  AssessmentLogs.AssessmentLogId OFFSET 0 ROWS FETCH FIRST 40 ROWS ONLY

Schema Sample

CREATE TABLE [dbo].[Client](
    [ClientId] [int] NOT NULL,
    [OrganizationName] [varchar](max) NULL,
    [Email] [varchar](max) NULL,
    [WorkPhone] [varchar](max) NULL,
    [Fax] [varchar](max) NULL,
    [UpdatedDate] [datetime] NULL,
    [Notes] [text] NULL,
    CONSTRAINT [PK_Clients] PRIMARY KEY CLUSTERED 
    (
        [ClientId] ASC
    )
);

CREATE TABLE [dbo].[AssessmentLog](
    [AssessmentLogId] [int] IDENTITY(1,1) NOT NULL,
    [ClientID] [int] NOT NULL,
    [PatientID] [int] NOT NULL,
    [AssessmentTypeCd] [int] NOT NULL,
    [Note] [varchar](max) NULL,
    [Void] [bit] NOT NULL,
    [DateInserted] [datetime] NOT NULL,
    [DateCharged] [datetime] NULL,
    CONSTRAINT [PK_AssessmentLog] PRIMARY KEY CLUSTERED 
    (
        [AssessmentLogId] ASC
    )
);

CREATE TABLE [dbo].[Patient](
    [PatientId] [int] IDENTITY(1,1) NOT NULL,
    [ClientId] [int] NOT NULL,
    [FirstName] [varchar](max) NOT NULL,
    [LastName] [varchar](max) NOT NULL,
    [MedicalRecordNbr] [varchar](max) NOT NULL,
    CONSTRAINT [PK_Patient] PRIMARY KEY CLUSTERED 
    (
        [PatientId] ASC
    )
);

CREATE TABLE [dbo].[AssessmentType](
    [AssessmentTypeCd] [int] IDENTITY(1,1) NOT NULL,
    [AssessmentTypeShort] [varchar](50) NULL,
    [AssessmentTypeLong] [varchar](max) NULL,
    CONSTRAINT [PK_AssessmentType] PRIMARY KEY CLUSTERED 
    (
        [AssessmentTypeCd] ASC
    )
);

CREATE TABLE [dbo].[EmrSystem](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](50) NOT NULL,
    [Created] [datetime2](7) NULL,
    [Modified] [datetime2](7) NULL,
    CONSTRAINT [PK_EmrSystem] PRIMARY KEY CLUSTERED 
    (
        [ID] ASC
    )
);

CREATE TABLE [dbo].[ClientEmrSystem](
    [ClientId] [int] NOT NULL,
    [EmrSystemId] [int] NOT NULL,
    [Created] [datetime2](7) NULL,
    [Modified] [datetime2](7) NULL,
    CONSTRAINT [PK_ClientEmrSystem] PRIMARY KEY CLUSTERED 
    (
        [ClientId] ASC,
        [EmrSystemId] ASC
    )
);

Sample Records

Here's sample records. I've specified primary keys even though they're auto-increment/identity columns just for simplicity:

INSERT INTO [dbo].[Client] (
    [ClientId],
    [OrganizationName],
    [Email],
    [WorkPhone],
    [Fax],
    [UpdatedDate],
    [Notes],
) VALUES (
    123,
    'Sample Client',
    'sample@sample.com',
    '(555) 555-1234',
    '(555) 555-5678',
    '2016-12-12 12:00:00',
    'Sample notes about sample client.'
);

INSERT INTO [dbo].[Patient] (
    [PatientId],
    [ClientId], 
    [FirstName],
    [LastName],
    [MedicalRecordNbr]
) VALUES (
    1,
    123,
    'Some',
    'Dude',
    'A12345'
);

INSERT INTO [dbo].[AssessmentType] (
    [AssessmentTypeCd],
    [AssessmentTypeShort],  
    [AssessmentTypeLong]
) VALUES (
    1,
    'Sample',
    'Sample Chart'
);

INSERT INTO [dbo].[EmrSystem] (
    [ID],
    [Name], 
    [Created],
    [Modified]
) VALUES (
    1,
    'Some System',
    GETDATE(),
    GETDATE()
);

INSERT INTO [dbo].[ClientEmrSystem] (
    [ClientId], 
    [EmrSystemId],
    [Created],
    [Modified]
) VALUES (
    123,
    1,
    GETDATE(),
    GETDATE()
);  

INSERT INTO [dbo].[AssessmentLog] (
    [ClientID],
    [PatientID],
    [AssessmentTypeCd],
    [Note],
    [Void],
    [DateInserted],
    [DateCharged]
) VALUES (
    123,
    1,
    1,
    'Sample notes',
    0,
    GETDATE(),
    NULL
);

Oddly, when using the EMR System filter, the fields under the Client association are not limited either. It returns everything. I tried adjusting the contain[] array to include the limited fields, but everything is ignored.

I'm assuming I did something wrong and its not Cake's fault, but I can't seem to figure out an elegant solution. Thank you for any help :) It is greatly appreciated.

  • Just a very wild guess.. I remember a similar problem, but cannot really remember unfortunately .. have you tried [innerJoinWith](http://book.cakephp.org/3.0/en/orm/query-builder.html#using-innerjoinwith) instead of `matching` – harpax Dec 06 '16 at 19:12
  • Thank you for the suggestion but yes, I have tried innerJoinWith() and the same issue remains. The association data is still missing from the result. – Kyle Weishaupt Dec 06 '16 at 21:32
  • 1
    Can you post a table schema and a limited set of records that should replicate the issue? – Jose Diaz-Gonzalez Dec 11 '16 at 08:24
  • Just added a sample of the schema and some sample records. Thanks. – Kyle Weishaupt Dec 12 '16 at 21:07
  • Have you ever found the reason for this, or a solution? I am currently facing a similar issue with CakePHP 3.7.5 – Christian Kirchhoff Jul 02 '19 at 08:21
  • @ChristianKirchhoff Sorry I haven't found a way to do this in one pass using the ORM. In my application I ended up doing a second query from the results of clients matching the EMR system. Hope that helps! – Kyle Weishaupt Jul 03 '19 at 15:05
  • Have you tried including a `['contain' => 'Client']` as array options in find()? – Paul Trimor Oct 19 '21 at 01:15

0 Answers0