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

Lockpick Laravel Package

marcin-orlowski/lockpick

Lockpick is a Laravel package that offers a clean, fluent API for working with cache-based locks. It simplifies acquiring, releasing, and inspecting locks, with support for timeouts and custom lock names, making concurrency control easier in Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer — it is a plain library with no service provider, no config file and no facade to register:

composer require marcin-orlowski/lockpick

The package is a tiny reflection/closure-binding wrapper exposing a handful of static methods (get, set, call, const — verify exact names against the README, the surface is small enough to read in one sitting). Because it ships no Laravel integration, the first thing to decide is where you are allowed to call it: in almost every project that means tests/ only, plus a short-lived shim during a legacy refactor.

The canonical first use case is writing a test for an object that has no public accessor:

use MarcinOrlowski\Lockpick\Lockpick;

final class LegacyReport
{
    private array $rows = [];

    private function formatRow(array $row): string
    {
        return strtoupper($row['name']);
    }
}

$report = new LegacyReport();

// Arrange: seed private state without a public setter
Lockpick::set($report, 'rows', [['name' => 'alpha']]);

// Act/Assert: inspect state the class does not expose
$this->assertSame([['name' => 'alpha']], Lockpick::get($report, 'rows'));

// Exercise a private collaborator in isolation
$this->assertSame('ALPHA', Lockpick::call($report, 'formatRow', [['name' => 'alpha']]));

That is 90% of real-world usage: unblocking a test against code you cannot (yet) change.

Implementation Patterns

Wrap it once, in a base TestCase. Do not let Lockpick:: calls scatter across hundreds of tests — you lose the ability to swap or remove the dependency later. Add two or three protected helpers to your TestCase:

protected function private(object $obj, string $property): mixed
{
    return Lockpick::get($obj, $property);
}

protected function callPrivate(object $obj, string $method, array $args = []): mixed
{
    return Lockpick::call($obj, $method, $args);
}

Now the package is replaceable by Closure::bind in five lines if it ever goes unmaintained.

Arrange-phase seeding instead of building a 30-argument constructor. Objects with large constructor graphs and no factories are the sweet spot. Seed only the properties the code under test actually reads, and leave the rest alone:

$order = new Order();                       // ctor does too much
Lockpick::set($order, 'status', Order::STATUS_PAID);
Lockpick::set($order, 'total', 1999);

Assert on private collections when there is no getter. Read private array $events (queued jobs, dispatched domain events, buffered log lines) and assert on the contents rather than asserting on side effects elsewhere.

Call a private method to test a rule without standing up the whole flow. This is the strongest pattern for legacy code: a private applyDiscount() or isEligible() with five branches can be exercised directly, in a table-driven data provider, instead of through three layers of public API.

Read private and protected constants from vendor classes. Enum-like vendor classes often keep algorithm IDs, status codes or plan tiers as private const. Lockpick::const(VendorClass::class, 'ALGO_RS256') removes the magic-string duplication from your tests and makes them fail loudly if the vendor renames the constant.

Shim-then-delete during refactors. When you are extracting a class out of a god object, expose the new collaborators through a thin adapter, migrate call sites to the adapter, and delete every Lockpick:: call before merging. Treat each usage as a TODO with a ticket number in the docblock.

Isolate from production code with a narrow interface. If you genuinely must reach private state in production (a badly-behaved third-party SDK), define an interface for the specific reads you need, implement it with Lockpick, and bind it in a service provider. That keeps the static utility out of your domain layer and gives you one file to rewrite when the vendor ships an accessor.

Gotchas and Tips

Inherited private members need the declaring class as scope. The usual Closure::bind($fn, $obj, get_class($obj)) trick only unlocks members declared on the concrete class. A private property or method living on a parent class stays invisible — you must bind to the declaring class (walk ReflectionClass::getParentClass() until hasProperty()/hasMethod() returns true). This is the single most common source of "why does this throw Error: Cannot access private property" reports, and it depends entirely on how the package implemented its binding — verify before relying on it with class hierarchies.

Static members are a different code path. Reading private static $registry or a private const requires binding with null as the instance and the class name as scope. Calling the instance API against a static member will not work.

readonly properties cannot be set. PHP 8.1 readonly properties reject writes even through reflection once initialized (and on 8.1, reflection writes throw at all). If your target has readonly state, Lockpick cannot help — you need to construct the object with the right values instead.

Uninitialized typed properties throw on read, not return null. Reading a private ?Foo $bar; that was never assigned raises Error: Typed property must not be accessed before initialization. Catch this in your helper and return a sentinel if you want friendlier assertion messages.

Mutation can leave invariants broken. Lockpick::set() bypasses validation, normalization and side-effect wiring. Tests that seed state this way can pass on a configuration that the real code path could never produce — a classic false green. Prefer calling the real public mutator when one exists.

Mock the object, not the visibility. Mockery::mock() / partialMock() cannot stub private methods at all. Refactor the private method into a protected one (or an injected collaborator) if you find yourself reaching for Lockpick in a mock setup — that is a design smell, not a testing problem.

Cache reflection in hot loops. If you call this inside a data provider or a loop over hundreds of fixtures, each call rebuilds closures/ReflectionProperty objects. Memoize by class::member in a small static array in your helper. For a test suite this rarely matters; for anything else it will.

Static analysis and IDE support are absent. Return types are mixed, so PHPStan/Psalm will flag every call, and your IDE will not resolve the property. Add inline /** @var Foo $x */ annotations and consider a PHPStan stub or a typed wrapper method per access site.

Pin the version and read the source. One star and zero dependents means no community safety net; the entire package is a few hundred lines. Lock the constraint in composer.json ("^1.0" rather than "*"), skim the implementation once so you know exactly which reflection semantics you are relying on, and keep the wrapper helper described above so removal is a one-file change.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource