Introduction
In Week 1, we defined the framework architecture and set the direction.
In Week 2, we learned how the RSC GraphQL schema is structured and how to navigate it.
In Week 3, we implemented rkRscGetToken(), a secure OAuth2 authentication helper using service accounts with token caching.
In Week 4, we executed our first real GraphQL query and established the execution pattern the entire framework will follow.
In Week 5, we extended rkGetSLAs() into a production-ready function with cursor-based pagination and normalized retention data.
Now it is time to move toward actual protection data.
This week, we retrieve snapshot statistics directly from Rubrik Security Cloud using snappableConnection and encapsulate them in a reusable PHP helper:
rkGetSnapshotCount()Given any protected workload (a VM or a fileset), this function returns a structured breakdown of its snapshot state:
- Total snapshots
- Local snapshots
- SLA-driven snapshots
- On-demand snapshots
- Replica snapshots
- Archive snapshots
This is the class of data that drives operational reporting, compliance validation, protection gap detection, and backup visibility workflows. More importantly, building it correctly here sets the structural template for every workload-query function we add for the remainder of the series.
Understanding "Snappables" in Rubrik Security Cloud
Before writing a query, one concept needs to be clear: in RSC, any protected object capable of producing snapshots is called a Snappable.
This includes VMware Virtual Machines, filesets, MSSQL databases, Oracle databases, NAS shares, Kubernetes workloads, and cloud-native objects. The list is broad by design. RSC is a unified control plane, and snappableConnection is its unified interface for querying protection state across all of them.
This matters for how we design rkGetSnapshotCount(). Rather than building a separate function per workload type, we build one function that accepts a $objectType parameter. The GraphQL query structure is identical across types; only the enum value changes.
Practitioner Tip "If you are unsure whether a field accepts a string or an enum, the RSC GraphQL Playground tells you immediately. Enums in GraphQL are not quoted. Passing 'VmwareVirtualMachine' as a string will fail silently in some contexts and throw in others. Validate in the Playground first." |
The Query
Here is the snappableConnection query we will use:
query SnappableSnapshotCounts {
snappableConnection(
filter: {
objectType: VmwareVirtualMachine
}
first: 1000
) {
nodes {
name
fid
objectType
totalSnapshots
localSnapshots
localOnDemandSnapshots
localSlaSnapshots
replicaSnapshots
archiveSnapshots
}
}
}The key fields:
One thing to call out explicitly:
objectType: VmwareVirtualMachineThis is a GraphQL enum, not a string. It does not use quotes. When constructing this query dynamically in PHP, passing a quoted string here is an easy mistake, and the RSC API will reject it. We sanitize the value before injection to prevent this.
Practitioner Tip "Always validate your query in the RSC GraphQL Playground before writing PHP against it. If the Playground returns data, you have confirmed the enum value, field names, and response structure. When the PHP call fails, you already know the query is sound, which means the bug is in your HTTP layer or response handling, not the GraphQL logic." |
Building rkGetSnapshotCount()
The function reuses the authentication and GraphQL execution infrastructure built in Weeks 3 and 4. Its responsibilities are:
The function reuses the authentication and GraphQL execution infrastructure built in Weeks 3 and 4. Its responsibilities are:
- Sanitize the $objectType enum to prevent injection
- Build the query dynamically with the correct enum value
- Execute via rkCallGraphQL()
- Validate the response structure
- Normalize the results
- Optionally filter to a single named object
<?php
/**
* Retrieve snapshot statistics from Rubrik Security Cloud.
*
* @param string $accessToken
* @param string|null $objectName Optional: filter to a specific object by name
* @param string $objectType GraphQL enum (e.g. VmwareVirtualMachine, Fileset)
*
* @return array|null
*/
function rkGetSnapshotCount(
string $accessToken,
?string $objectName = null,
string $objectType = 'VmwareVirtualMachine'
): ?array
{
// ----------------------------------------
// Sanitize GraphQL enum: no quotes, no
// special characters
// ----------------------------------------
$objectTypeEnum = preg_replace(
'/[^A-Za-z0-9_]/',
'',
$objectType
);
// ----------------------------------------
// Build query with sanitized enum inline
// ----------------------------------------
$query = <<<GQL
query SnappableSnapshotCounts {
snappableConnection(
filter: {
objectType: $objectTypeEnum
}
first: 1000
) {
nodes {
name
fid
objectType
totalSnapshots
localSnapshots
localOnDemandSnapshots
localSlaSnapshots
replicaSnapshots
archiveSnapshots
}
}
}
GQL;
// ----------------------------------------
// Execute query
// ----------------------------------------
$response = rkCallGraphQL($accessToken, $query);
// ----------------------------------------
// Validate response structure
// ----------------------------------------
if (!isset($response['data']['snappableConnection']['nodes']))
{
throw new RuntimeException(
'Unexpected GraphQL response structure in rkGetSnapshotCount().'
);
}
$nodes = $response['data']['snappableConnection']['nodes'];
// ----------------------------------------
// Normalize results
// ----------------------------------------
$results = [];
foreach ($nodes as $node)
{
$results[] = [
'name' => $node['name'] ?? '',
'fid' => $node['fid'] ?? '',
'objectType' => $node['objectType'] ?? '',
'totalSnapshots' => $node['totalSnapshots'] ?? 0,
'localSnapshots' => $node['localSnapshots'] ?? 0,
'localOnDemandSnapshots' => $node['localOnDemandSnapshots'] ?? 0,
'localSlaSnapshots' => $node['localSlaSnapshots'] ?? 0,
'replicaSnapshots' => $node['replicaSnapshots'] ?? 0,
'archiveSnapshots' => $node['archiveSnapshots'] ?? 0,
];
}
// ----------------------------------------
// Return full result set if no name filter
// ----------------------------------------
if ($objectName === null || $objectName === '')
{
return $results;
}
// ----------------------------------------
// Return single matching object
// ----------------------------------------
foreach ($results as $item)
{
if ($item['name'] === $objectName)
{
return $item;
}
}
return null;
}
CLI Usage
A minimal script to exercise the function:
#!/usr/bin/env php
<?php
require_once 'RscFramework.php';
// Get OAuth token
$token = rkRscGetToken();
// Retrieve snapshot data for a specific VM
$result = rkGetSnapshotCount(
$token,
'PROD-VM-01',
'VmwareVirtualMachine'
);
print_r($result);Expected output:
Array
(
[name] => PROD-VM-01
[fid] => 12345678-aaaa-bbbb-cccc-1234567890ab
[objectType] => VmwareVirtualMachine
[totalSnapshots] => 42
[localSnapshots] => 30
[localOnDemandSnapshots] => 2
[localSlaSnapshots] => 28
[replicaSnapshots] => 8
[archiveSnapshots] => 4
)Supporting Multiple Workload Types
One of the direct benefits of building around snappableConnection is that the same function works unchanged for other workload types. To query a fileset instead of a VM:
$result = rkGetSnapshotCount(
$token,
'Finance Fileset',
'Fileset'
);No code changes. Only the enum value changes. The object model and response structure are consistent across workload types, which is exactly what a unified control plane like RSC is designed to provide.
Why Snapshot Counts Are a Foundation Workload
Retrieving snapshot counts looks simple on the surface. It is not a simple use case.
This data is the input for a wide class of operational workflows:
- Identify workloads with zero recent snapshots
- Detect systems where SLA-driven snapshots have stopped while on-demand counts continue to grow
- Validate that archival policies are producing archive snapshots as expected
- Build compliance reports that distinguish policy-driven coverage from manual activity
- Track snapshot growth over time to anticipate storage impacts
- Feed health dashboards or SIEM systems with structured protection state
Each of these workflows requires the same data structure we just built. Getting the normalization right here (consistent keys, zero-defaulted counts, nullable name filter) means every downstream workflow can trust the data it receives.
What We Have Now
At the end of Week 6, the framework has:
- Authentication: rkRscGetToken() with OAuth2 and token caching
- GraphQL execution: rkCallGraphQL() with dual-layer error handling (HTTP and GraphQL)
- Pagination: cursor-based, implemented correctly from day one
- SLA retrieval: rkGetSLAs() with retention normalization
- Snapshot statistics: rkGetSnapshotCount() across multiple workload types
This is no longer a proof-of-concept. It is a working operational toolkit.
What's Next
In Week 7, we move from snapshot counts to snapshot detail.
Where this week gives us how many snapshots exist, next week we retrieve which snapshots exist: timestamps, state, retention assignments, and age. That data is what enables real compliance and backup validation logic, including identifying coverage gaps, flagging snapshots approaching retention expiry, and building the foundation for a protection health report directly from PHP.
Contributed by

Frederic Lhoest
Senior Technology Architect, PCCW Global

Mike Preston
Staff Technical Marketing Manager, Rubrik








