Apex Programming

Apex Basics

// Class definition
public class MyApexClass {
    // Class variables
    private String name;
    public Integer count { get; set; }

    // Constructor
    public MyApexClass() {
        name = 'Default';
        count = 0;
    }

    // Method with parameters
    public String greet(String personName) {
        return 'Hello, ' + personName + '!';
    }

    // Static method
    public static Integer incrementCount(Integer current) {
        return current + 1;
    }
}

// Interface definition
public interface MyInterface {
    void doSomething();
}

// Exception class
public class MyException extends Exception {
    // Custom exception properties/methods
}

DML Operations

// Create records
Account acc = new Account(Name = 'Test Account');
insert acc;

// Update records
acc.Name = 'Updated Account';
update acc;

// Upsert (insert or update)
upsert acc;

// Delete records
delete acc;

// Undelete records
undelete acc;

// Bulk DML operations
List<Account> accounts = new List<Account>();
for (Integer i = 0; i < 10; i++) {
    accounts.add(new Account(Name = 'Account ' + i));
}
insert accounts;

// Database methods with options
Database.SaveResult sr = Database.insert(acc, false);
List<Database.SaveResult> results = Database.insert(accounts, false);

// Check DML results
for (Database.SaveResult result : results) {
    if (result.isSuccess()) {
        System.debug('Successfully created account: ' + result.getId());
    } else {
        for (Database.Error err : result.getErrors()) {
            System.debug('Error: ' + err.getMessage());
        }
    }
}

SOQL & SOSL

SOQL Queries

// Basic SOQL query
List<Account> accounts = [SELECT Id, Name FROM Account];

// Query with WHERE clause
List<Account> accounts = [SELECT Id, Name FROM Account WHERE Industry = 'Technology'];

// Query with relationship fields
List<Contact> contacts = [SELECT Id, Name, Account.Name FROM Contact];

// Query with ORDER BY and LIMIT
List<Account> accounts = [SELECT Id, Name FROM Account ORDER BY Name DESC LIMIT 10];

// Aggregate queries
AggregateResult[] groupedResults = [SELECT COUNT(Id) total, Industry FROM Account GROUP BY Industry];
for (AggregateResult ar : groupedResults) {
    System.debug('Industry: ' + ar.get('Industry'));
    System.debug('Count: ' + ar.get('total'));
}

// Dynamic SOQL
String accountName = 'Test%';
String query = 'SELECT Id, Name FROM Account WHERE Name LIKE :accountName';
List<Account> accounts = Database.query(query);

// SOQL For Loop (handles large result sets)
for (Account acc : [SELECT Id, Name FROM Account]) {
    System.debug('Account Name: ' + acc.Name);
}

// Query with bind variables
String industry = 'Technology';
List<Account> accounts = [SELECT Id, Name FROM Account WHERE Industry = :industry];

SOSL Queries

// Basic SOSL query
List<List<SObject>> searchList = [FIND 'Test' IN ALL FIELDS RETURNING Account(Id, Name), Contact];

// Extract results from SOSL query
List<Account> accounts = (List<Account>)searchList[0];
List<Contact> contacts = (List<Contact>)searchList[1];

// SOSL with WHERE clause
List<List<SObject>> searchList = [FIND 'Test' IN ALL FIELDS RETURNING Account(Id, Name WHERE Industry = 'Technology')];

// SOSL with multiple search groups
List<List<SObject>> searchList = [FIND 'Test OR Demo' IN ALL FIELDS RETURNING Account, Contact];

// Dynamic SOSL
String searchQuery = 'FIND \'Test*\' IN ALL FIELDS RETURNING Account (Id, Name), Contact';
List<List<SObject>> searchList = Search.query(searchQuery);

// SOSL with LIMIT and ORDER BY
List<List<SObject>> searchList = [FIND 'Test' IN ALL FIELDS RETURNING Account(Id, Name ORDER BY Name LIMIT 10)];

// SOSL with different search locations
List<List<SObject>> searchList = [FIND 'Test' IN NAME FIELDS RETURNING Account];

// SOSL with email fields search
List<List<SObject>> searchList = [FIND 'example.com' IN EMAIL FIELDS RETURNING Contact(Id, Name, Email)];

Triggers & Batch Apex

Apex Triggers

// Basic trigger structure
trigger AccountTrigger on Account (before insert, before update, after insert, after update) {
    // Trigger context variables
    if (Trigger.isBefore) {
        if (Trigger.isInsert) {
            // Before insert logic
            for (Account acc : Trigger.new) {
                // Set default values
                acc.Description = 'Created by trigger';
            }
        }
        else if (Trigger.isUpdate) {
            // Before update logic
            for (Account acc : Trigger.new) {
                Account oldAcc = Trigger.oldMap.get(acc.Id);
                if (acc.Name != oldAcc.Name) {
                    acc.Description = 'Name changed from ' + oldAcc.Name + ' to ' + acc.Name;
                }
            }
        }
    }
    else if (Trigger.isAfter) {
        if (Trigger.isInsert) {
            // After insert logic
            List<Task> tasks = new List<Task>();
            for (Account acc : Trigger.tasks.add(new Task(
                    Subject = 'Follow up on new account',
                    WhatId = acc.Id,
                    Status = 'Not Started'
                ));
            }
            insert tasks;
        }
    }
}

// Trigger handler pattern (recommended)
trigger AccountTrigger on Account (before insert, before update) {
    AccountTriggerHandler.handleBeforeInsertUpdate(Trigger.new, Trigger.oldMap);
}

Batch Apex

// Batch Apex class structure
global class AccountUpdateBatch implements Database.Batchable<SObject> {
    
    // Start method
    global Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(
            'SELECT Id, Name, Description FROM Account WHERE CreatedDate = TODAY'
        );
    }
    
    // Execute method
    global void execute(Database.BatchableContext bc, List<Account> scope) {
        List<Account> accountsToUpdate = new List<Account>();
        for (Account acc : scope) {
            acc.Description = 'Updated by batch job on ' + System.today();
            accountsToUpdate.add(acc);
        }
        update accountsToUpdate;
    }
    
    // Finish method
    global void finish(Database.BatchableContext bc) {
        // Execute any post-processing operations
        AsyncApexJob job = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed, TotalJobItems, CreatedBy.Email FROM AsyncApexJob WHERE Id = :bc.getJobId()];
        System.debug('Batch job completed with status: ' + job.Status);
    }
}

// Executing a batch job
AccountUpdateBatch batchJob = new AccountUpdateBatch();
Database.executeBatch(batchJob, 100); // 100 records per batch

// Schedule a batch job
String cronExpr = '0 0 2 * * ?'; // Run at 2 AM every day
System.schedule('Daily Account Update', cronExpr, new AccountUpdateBatch());

// Batchable with state
global class StatefulBatch implements Database.Batchable<SObject>, Database.Stateful {
    global Integer recordsProcessed = 0;
    
    global Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator('SELECT Id FROM Account');
    }
    
    global void execute(Database.BatchableContext bc, List<Account> scope) {
        recordsProcessed += scope.size();
    }
    
    global void finish(Database.BatchableContext bc) {
        System.debug('Total records processed: ' + recordsProcessed);
    }
}

Lightning Web Components

LWC Basics

// JavaScript controller (myComponent.js)
import { LightningElement, track, wire } from 'lwc';
import getContacts from '@salesforce/apex/ContactController.getContacts';

export default class MyComponent extends LightningElement {
    @track contacts = [];
    @track error;
    searchKey = '';

    // Wire method to get data from Apex
    @wire(getContacts, { searchKey: '$searchKey' })
    wiredContacts({ error, data }) {
        if (data) {
            this.contacts = data;
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.contacts = [];
        }
    }

    // Handle user input
    handleSearchChange(event) {
        this.searchKey = event.target.value;
    }

    // Method to create a new contact
    createContact() {
        this.template.querySelector('c-contact-form').show();
    }
}

// Apex controller (ContactController.cls)
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContacts(String searchKey) {
        String query = 'SELECT Id, Name, Email, Phone FROM Contact';
        if (searchKey != null && searchKey != '') {
            query += ' WHERE Name LIKE \'%' + String.escapeSingleQuotes(searchKey) + '%\'';
        }
        return Database.query(query);
    }
}

LWC HTML Template


<template>
    <div class="slds-m-around_medium">
        <h1>Contact Manager</h1>
        
        <!-- Search input -->
        <div class="slds-grid slds-gutters">
            <div class="slds-col">
                <lightning-input
                    type="search"
                    value="{searchKey}"
                    onchange="{handleSearchChange}"
                    label="Search"
                    placeholder="Search contacts...">
                </lightning-input>
            </div>
            <div class="slds-col slds-size_1-of-4">
                <lightning-button
                    label="New Contact"
                    variant="brand"
                    onclick="{createContact}">
                </lightning-button>
            </div>
        </div>
        
        <!-- Display error if any -->
        <template if:true="{error}">
            <div class="slds-notify slds-notify_alert slds-theme_error">
                <span class="slds-assistive-text">Error</span>
                <span class="slds-icon_container slds-icon-utility-error">
                    <lightning-icon icon-name="utility:error"></lightning-icon>
                </span>
                <h2>{error.body.message}</h2>
            </div>
        </template>
        
        <!-- Contacts list -->
        <template if:true="{contacts}">
            <div class="slds-card">
                <ul class="slds-list slds-list_vertical">
                    <template for:each="{contacts}" for:item="contact">
                        <li key="{contact.Id}" class="slds-list__item">
                            <div class="slds-grid slds-wrap">
                                <div class="slds-col slds-size_1-of-2">
                                    <b>{contact.Name}</b>
                                </div>
                                <div class="slds-col slds-size_1-of-2">
                                    <a href="tel:{contact.Phone}">{contact.Phone}</a>
                                </div>
                            </div>
                        </li>
                    </template>
                </ul>
            </div>
        </template>
    </div>
    
    <!-- Contact form component -->
    <c-contact-form></c-contact-form>
</template>

Integration & Deployment

REST API Integration

// Apex REST Web Service
@RestResource(urlMapping='/Account/*')
global with sharing class AccountManager {
    
    // HTTP GET method
    @HttpGet
    global static Account getAccount() {
        RestRequest request = RestContext.request;
        String accountId = request.requestURI.substring(request.requestURI.lastIndexOf('/')+1);
        Account result = [SELECT Id, Phone, Website FROM Account WHERE Id = :accountId];
        return result;
    }
    
    // HTTP POST method
    @HttpPost
    global static String createAccount(String name, String phone, String website) {
        Account acc = new Account();
        acc.Name = name;
        acc.Phone = phone;
        acc.Website = website;
        insert acc;
        return acc.Id;
    }
    
    // HTTP PATCH method
    @HttpPatch
    global static String updateAccount() {
        RestRequest request = RestContext.request;
        String accountId = request.requestURI.substring(request.requestURI.lastIndexOf('/')+1);
        Account acc = [SELECT Id FROM Account WHERE Id = :accountId];
        Map<String, Object> params = (Map<String, Object>)JSON.deserializeUntyped(request.requestbody.toString());
        for (String fieldName : params.keySet()) {
            acc.put(fieldName, params.get(fieldName));
        }
        update acc;
        return 'Account updated successfully';
    }
}

// Callout to external REST API
public class HttpCalloutService {
    public static String makeGetCallout(String endpoint) {
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint(endpoint);
        request.setMethod('GET');
        HttpResponse response = http.send(request);
        if (response.getStatusCode() == 200) {
            return response.getBody();
        } else {
            throw new CalloutException('HTTP callout failed with status: ' + getStatusCode());
        }
    }
}

Deployment & Tools

# Salesforce CLI commands
# Login to org
sfdx force:auth:web:login -a "MyOrg"

# Create a scratch org
sfdx force:org:create -f config/project-scratch-def.json -a "MyScratchOrg" -s

# Push source to org
sfdx force:source:push -u "MyScratchOrg"

# Pull changes from org
sfdx force:source:pull -u "MyScratchOrg"

# Run Apex tests
sfdx force:apex:test:run -u "MyScratchOrg" -r "human"

# Open org
sfdx force:org:open -u "MyScratchOrg"

# Retrieve metadata
sfdx force:source:retrieve -m "ApexClass,CustomObject"

# Deploy metadata
sfdx force:source:deploy -m "ApexClass:MyClass,CustomObject:Account"

# Create a new Apex class
sfdx force:apex:class:create -n "MyNewClass" -d force-app/main/default/classes

# Create a new LWC
sfdx force:lightning:component:create -n "myComponent" -d force-app/main/default/lwc

# Generate password for user
sfdx force:user:password:generate -u "MyScratchOrg"

# Display org info
sfdx force:org:display -u "MyScratchOrg"

# List all orgs
sfdx force:org:list

# Delete a scratch org
sfdx force:org:delete -u "MyScratchOrg"

# Package version commands
sfdx force:package:version:create -p "MyPackage" -d force-app -w 10
sfdx force:package:version:list
sfdx force:package:version:promote -p "04t..."

Salesforce Resources

Useful Links

Best Practices

// Bulkify your code - handle multiple records
// Bad practice - SOQL inside loop
for (Account acc : accounts) {
    List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id]; // Avoid!
}

// Good practice - bulk SOQL
Set<Id> accountIds = new Set<Id>();
for (Account acc : accounts) {
    accountIds.add(acc.Map<Id, List<Contact>> accountContacts = new Map<Id, List<Contact>>();
for (con : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
    if (!accountContacts.containsKey(con.AccountId)) {
        accountContacts.put(con.AccountId, new List<Contact>());
    }
    accountContacts.get(con.AccountId).add(con);
}

// Avoid hardcoding IDs
// Bad practice
Account acc = [SELECT Id FROM Account WHERE Id = '001...']; // Avoid!

// Good practice - use custom settings or custom metadata
String accountId = AppConfig__c.getInstance().DefaultAccountId__c;
Account acc = [SELECT Id FROM极 Account WHERE Id = :accountId];

// Use limits methods to avoid governor limits
Integer soqlQueries = Limits.getQueries();
Integer soqlLimit = Limits.getLimitQueries();
System.debug('SOQL queries used: ' + soqlQueries + ' of ' + soqlLimit);

// Use @future appropriately for async operations
@future
public static void processRecordsAsync(Set<Id> recordIds) {
    // Async processing logic
}

// Use exception handling
try {
    // Code that might fail
    insert records;
} catch (DmlException e) {
    System.debug('DML Exception: ' + e.getMessage());
    // Handle exception appropriately
}