../courses.php
CRM
CRM101
90
Leads, Opportunities, And Sales
Learn how a CRM tracks possible customers before they become real customers.
Two contacts in the JSON file look almost identical — both have a name and an email — except one has
"status": "lead" and the other has
"status": "customer". Why does one extra field change what these two records actually mean?
A lead is someone who might buy something; a customer is someone who already has. The contact data itself — name, email — doesn't capture that distinction at all on its own; only the
status
field does. Adding one field turns a flat contact list into something that can actually answer "who haven't we converted yet," which a plain list of names and emails never could on its own.
A lead is a contact who hasn't bought anything yet — someone worth following up with. A customer is a contact who already has. The two need to be told apart, or a CRM can't help anyone decide who to actually call next.
A single new field on the existing contact object —
status
— is enough to capture this distinction, the same nesting-free approach earlier JSON lessons used before reaching for more complex structure than a problem actually needs.
Filtering contacts by status uses the exact same array-filtering pattern covered for JavaScript earlier in this course's prerequisite material — PHP's
array_filter()
does the same job, just with PHP's own syntax.
A dropdown in the form, instead of a free-text field, limits status to a known, small set of values — lead, customer — rather than letting someone type anything at all into what's meant to be a controlled category.
This lesson adds a status field to every contact, a way to set it when adding a new one, and a filtered view that shows only leads — turning the flat list from earlier lectures into something that can actually answer a real sales question.
The CRM, stage three: tracking lead vs. customer status
<?php
$contactsFile = __DIR__ . '/contacts.json';
$contacts = json_decode(file_get_contents($contactsFile), true);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$contacts[] = [
'name' => $_POST['name'],
'email' => $_POST['email'],
'status' => $_POST['status']
];
file_put_contents($contactsFile, json_encode($contacts));
}
$leads = array_filter($contacts, function ($c) {
return $c['status'] === 'lead';
});
?>
<form method="POST">
<input name="name" placeholder="Name">
<input name="email" placeholder="Email">
<select name="status">
<option value="lead">Lead</option>
<option value="customer">Customer</option>
</select>
<button type="submit">Add Contact</button>
</form>
<h2>Leads</h2>
<?php foreach ($leads as $lead) { ?>
<div><?php echo $lead['name']; ?></div>
<?php } ?>
'status' => $_POST['status'] — A new key added onto the same contact array shape from lecture 2 — no restructuring needed, just one more field on the same record.
array_filter($contacts, function ($c) { ... }) — Builds a new array containing only the contacts where the inner function returns true, the same shape as
.filter()
covered for JavaScript arrays earlier in this course.
return $c['status'] === 'lead'; — The actual filtering condition: keep this contact only if its status field is exactly the string
'lead'.
<select name="status"> — A dropdown instead of a free-text input, limiting what
$_POST['status'] can actually contain to exactly the two options listed, rather than whatever text someone types.
The leads section, filtered fresh every page load — Just like the contacts list in earlier lectures, this filtered view is recalculated from the current JSON every time the page runs — it isn't cached or saved separately anywhere.
One field can be the difference between a flat list and a useful one
Without
status, "show me everyone we haven't converted yet" is a question this data simply cannot answer — adding the one field is what makes an entire category of useful question answerable at all.
A dropdown prevents a whole category of inconsistent data
A free-text status field would let someone type "Lead," "lead," "LEAD," or "potential" for the same underlying meaning — a dropdown with exactly two fixed values means
=== 'lead'
reliably catches every actual lead, with no risk of a typo or inconsistent capitalization slipping past the filter.
Existing contacts without a status field would break this filter silently
If a contact from lecture 1 or 2's JSON file never got a
status
key added retroactively, reading
$c['status']
on it would produce a PHP warning instead of a clean false — old data needs to actually be updated to match a new field, not just assumed to already have it.
array_filter preserves the original array keys, which can surprise a loop later
Filtering a numerically indexed array can leave gaps in the index numbers — item 0 and item 2 might remain while item 1 is filtered out — which rarely matters for a simple
foreach, but is worth knowing before relying on the filtered array's keys being sequential.
This same status-field pattern extends directly to the next lesson's pipeline stages
Lead and customer are really just two stages — the next lecture's reminders and pipeline stages build on exactly this same idea, just with more than two possible values for the field.
A free-text status field — avoid
<input name="status" placeholder="lead or customer">
A controlled dropdown — use this
<select name="status">
<option value="lead">Lead</option>
<option value="customer">Customer</option>
</select>
Paste your PHP code into ChatGPT and ask: "Is the status field a controlled dropdown or free text, and would an existing contact missing the status field break the filter?" It's good at spotting both, though testing with an actual old contact missing the field is worth doing directly.
Add a
status
field to every existing contact in your JSON file by hand.
Add the dropdown to your form and confirm a new contact's status is saved correctly.
Add the leads-only filtered section and confirm it shows only contacts with
status
set to lead.
Add one contact to the JSON file by hand with no status field at all, and observe what happens when the filter runs.
Add a second filtered section showing only customers, reusing the same
array_filter()
pattern with a different condition.
Using a free-text field for status instead of a dropdown, allowing inconsistent values that a filter checking for an exact match would then silently miss.
Adding a new field to the data shape going forward without updating existing records, leaving old contacts in a different, incompatible shape.
Assuming
array_filter()
re-indexes the resulting array starting from zero, when it actually preserves the original keys.
Hardcoding the filter condition's exact string in multiple places, risking a typo in one of them that silently breaks just that one filter.
Treating lead and customer as the only two states a contact could ever need, when a real pipeline often needs more stages, covered in the next lesson.
Add a status field to a contact record, filter an array by that field with array_filter, and explain why a controlled dropdown prevents inconsistent data a filter would otherwise miss.
Login - Jit4All
Login
Welcome back to Jit4All