Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Parse Laravel Package

graham-campbell/parse

Laravel integration for the Parse PHP SDK, letting you use the Parse Platform backend (parse-server) in your Laravel apps. Includes a service provider, facade alias, and publishable config for quickly setting up your Parse application credentials and client.

View on GitHub
Deep Wiki
Context7

Getting Started

Understand the scope before you install it. graham-campbell/parse is a bootstrap, not an ORM. It registers a service provider that calls ParseClient::initialize() with credentials from config/parse.php, binds a container entry, and exposes a facade. Every object, query, user, file, and cloud-function call you make afterwards is plain parse/php-sdk. Budget your learning time accordingly: 10 minutes on the bridge, an afternoon on the SDK.

composer require graham-campbell/parse

Registration. Check vendor/graham-campbell/parse/composer.json for extra.laravel.providers. On Laravel 5.5+ with auto-discovery present, you're done. Otherwise register manually:

// config/app.php
'providers' => [
    GrahamCampbell\Parse\ParseServiceProvider::class,
],
'aliases' => [
    'Parse' => GrahamCampbell\Parse\Facades\Parse::class,
],

Publish the config and bind it to .env:

php artisan vendor:publish --provider="GrahamCampbell\Parse\ParseServiceProvider" --tag="config"
PARSE_APP_ID=your_app_id
PARSE_REST_KEY=your_rest_key
PARSE_MASTER_KEY=your_master_key      # bypasses ACLs — see Gotchas
PARSE_SERVER_URL=https://parse.example.com
PARSE_MOUNT_PATH=/parse

Open config/parse.php next. That file plus the provider is the entire bridge — there are no repositories, no query builder, no exception mapping hiding elsewhere. Confirm what the facade actually forwards to by reading vendor/graham-campbell/parse/src/Facades/Parse.php; versions differ on whether you get a manager (Parse::connection()), a client instance, or static passthrough. Two minutes there saves an hour of BadMethodCallException.

Keep the parse-php-sdk docs pinned in a tab. That's your real reference.

Smoke test before writing a line of feature code. One CRUD round-trip from php artisan tinker proves credentials, mount path, and network reachability simultaneously:

use GrahamCampbell\Parse\Facades\Parse;

$task = Parse::object('Task');
$task->set('title', 'Ship the feature');
$task->set('done', false);
$task->save();                        // populates getObjectId()

$open = Parse::query('Task')
    ->equalTo('done', false)
    ->descending('createdAt')
    ->limit(20)
    ->find();

dump(count($open), $open[0]->getObjectId());

A ParseConnectionException here is almost always SERVER_URL or PARSE_MOUNT_PATH — check those before you start rotating keys. An auth error (401) is keys; a 404 on every class is the mount path.

First real use case. Write one repository class around a single Parse class (Task) exposing find, create, update, delete. Wire it through the container and inject it. That gives you one seam to fake in tests, one place to translate SDK exceptions into Laravel ones, and one place to swap for raw REST if the bridge ever becomes a problem. Do this on day one, not after the fifth controller starts calling Parse::query() directly.

Implementation Patterns

Never call the facade from a controller. The SDK is static, stringly-typed, and throws its own exception hierarchy. Wrap it:

final class TaskRepository
{
    public function __construct(private readonly string $class = 'Task') {}

    public function open(int $limit = 50): array
    {
        return Parse::query($this->class)
            ->equalTo('done', false)
            ->descending('createdAt')
            ->limit($limit)
            ->find();
    }

    public function complete(string $objectId): void
    {
        $task = Parse::object($this->class);
        $task->set('objectId', $objectId);
        $task->set('done', true);
        $task->save();
    }
}

Bind TaskRepository (or an interface) in a service provider so tests and future migrations have a swap point.

Use atomic operators for counters and collections. get() → mutate → set() → save() loses concurrent updates. increment, addUnique, add, remove compile to server-side operations:

$stats = Parse::object('Stats');
$stats->set('objectId', $id);
$stats->increment('views', 1);
$stats->addUnique('tags', ['laravel']);
$stats->save();

The same applies to remove/add on arrays — never rewrite the whole array from PHP if two writers can race.

Model relationships as pointers, not foreign-key strings. Pointers hydrate natively and support eager loading:

$project = Parse::object('Project');
$project->set('objectId', $projectId);

$task = Parse::object('Task');
$task->set('project', $project);    // stored as a Parse pointer
$task->save();

Kill N+1 at query time with includeKey:

$tasks = Parse::query('Task')
    ->includeKey('project')
    ->equalTo('done', false)
    ->find();

foreach ($tasks as $task) {
    $task->get('project')->get('name');   // already hydrated, no extra HTTP
}

Batch and chunk. One HTTP call per object is how Parse integrations get slow. Parse Server caps batch writes around 50 objects and query results around 100 by default:

foreach (array_chunk($objects, 50) as $chunk) {
    \Parse\ParseObject::saveAll($chunk);
}

Chunk at 50 even if your server allows more — it keeps individual request latency and failure blast radius sane.

Push write-heavy and cloud-function traffic to the queue. Every Parse call is an outbound HTTP request inside your request lifecycle. Anything writing more than a couple of objects, or calling Cloud Code, belongs in a job:

dispatch(new SyncTaskToParse($taskId))->onQueue('parse');

Give the queue its own worker and retry policy so a Parse wobble doesn't back up your mail queue.

Prefer Cloud Code over reimplementing server logic in PHP. If the Parse app already has beforeSave hooks and functions, call them rather than duplicating rules client-side:

$result = \Parse\ParseCloud::run('sendDigest', ['userId' => $id]);

Keep the master key off the request path. It bypasses ACLs and CLPs. Restrict it to queued jobs, admin commands, provisioning, and seeders. Anything user-facing should authenticate with the REST key plus ACLs, or a real ParseUser session. If a request genuinely needs elevation, gate it behind a Laravel policy first, then perform the elevated call inside the repository.

Users: decide who owns identity, then enforce it. Parse::user() creates or signs in a ParseUser — that is a Parse session, unrelated to Auth::user():

$user = Parse::user();
$user->setUsername($email);
$user->setPassword($password);
$user->setEmail($email);
$user->signUp();

Recommended for anything with a Laravel front end: Laravel owns identity, Parse users are provisioned records created from jobs/commands, and Parse sessions are never used for browser auth.

Files: save, attach, cache the URL.

$file = \Parse\ParseFile::createFromData($bytes, 'avatar.png', 'image/png');
$file->save();

$user = Parse::object('_User');
$user->set('objectId', $id);
$user->set('avatar', $file);
$user->save();

If you need to query or render files without a Parse round-trip, persist the returned URL in your own database at write time.

Serialise explicitly. find() hands back ParseObject instances with protected properties — json_encode on them produces garbage and return $task; from a controller leaks internals. Map into an API Resource that calls get() for each exposed field, with defaults for missing keys.

Testing with a mocked HTTP layer. Inject a Guzzle handler stack so you never hit a live server in CI:

ParseClient::setHttpClient(new Client(['handler' => $mockStack]));

Because the client is static, reset it in tearDown() or your second test inherits the first test's client and config changes silently stop applying.

Gotchas and Tips

The SDK is globally static — there is no per-connection model. ParseClient::initialize() runs once during provider boot. Two Parse apps in one process, or per-tenant credentials, means re-initialising (and losing any in-flight client state) or running separate processes. Decide whether multi-tenancy is even viable before designing around this bridge; retrofitting it later is painful.

Changing config after boot does nothing. config(['parse.rest_key' => ...]) in a middleware or test won't reach the already-initialised static client. php artisan config:cache is fine (the provider reads config at boot), but runtime overrides require an explicit re-initialise. This is the single most common source of "my test config isn't being used."

The master key is a loaded gun. Bypassing CLPs means a bug in one query leaks every row in the class. Never put it in .env-consumed request code paths, never echo it in error responses, never put it in job payloads. Rotate immediately if it appears in a log. Prefer a separate queue worker / process that holds the master key over branching on it at runtime.

Parse::user() is not Auth::user() and never will be. No silent syncing, no dual-session cleverness. Write the decision down (Laravel owns identity) and check for violations in review.

Exceptions are entirely your problem. Everything throws ParseException subclasses, which Laravel renders as generic 500s. Map them in the repository boundary:

try {
    return $task->fetch();
} catch (\Parse\ParseResourceNotFoundException $e) {
    throw new ModelNotFoundException("Task {$objectId} not found");
} catch (\Parse\ParseConnectionException $e) {
    report($e);
    throw new ServiceUnavailableException('Parse backend unreachable');
} catch (\Parse\ParseException $e) {
    report($e);
    throw new \RuntimeException('Parse operation failed', 0, $e);
}

Callers should never see a Parse* class name. That's what makes the eventual swap-out tractable.

There is no schema management. Classes are created implicitly on first write. 'Tasks' vs 'Task' silently creates an empty class, and your find() returns [] forever with no error. Put class names in constants (TaskRepository::CLASS), and use the Parse dashboard to verify a new class actually appeared after your first write.

Pagination is skip/limit and it isn't cursor-safe. limit(1000) gets rejected by Parse Server (default cap 100). Page with skip/limit ordered by createdAt or objectId, and accept that offset paging over a mutating collection will drop or duplicate rows — for anything correctness-critical, page by greaterThan('objectId', $last) instead.

Timeouts and retries aren't managed for you. The default Guzzle client will hang on a sick Parse Server and eat your PHP-FPM workers. Set connect_timeout and timeout on the HTTP client, wrap risky calls in retry() with backoff inside jobs, and consider a circuit breaker if Parse is on any critical path.

Dependency pinning is non-optional. This is a Laravel 5-era bridge over an ageing SDK. A casual composer update can pull an incompatible Guzzle or parse/php-sdk. Pin both graham-campbell/parse and parse/php-sdk to exact versions, and vendor/fork if you need a patch — upstream is effectively unmaintained. Treat any Laravel major upgrade as a project with a spike, not an afternoon.

Observability is on you. All traffic funnels through one static client, so a Parse outage and a bug in your query both look like a 500. Add a Guzzle middleware that logs URI, status, duration, and exception class around the repository, and emit a metric per call. Without that, you're debugging blind.

Extension points are thin: the HTTP handler, the initialise call, and the config file. That's it. Wrap, don't patch — put customisation in your own repository/adapter layer and keep the option to bypass the bridge with direct REST calls open. If the package is load-bearing, that adapter is your exit plan, so write it as if you'll need it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle